diff --git a/.gitignore b/.gitignore index 9b5677c4..da9e977b 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,5 @@ data.sql #### docs #### !**/src/main/resources/static/docs/ *.html -firebase-key.json \ No newline at end of file + +firebase-key.json diff --git a/build.gradle b/build.gradle index bd351e8c..2fd7bb34 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-batch' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' @@ -90,6 +91,7 @@ dependencies { implementation platform('software.amazon.awssdk:bom:2.17.230') implementation 'software.amazon.awssdk:s3' + } tasks.named('test') { diff --git a/src/main/java/com/moing/backend/domain/fire/application/service/FireThrowAlarmUseCase.java b/src/main/java/com/moing/backend/domain/fire/application/service/FireThrowAlarmUseCase.java new file mode 100644 index 00000000..83eb1b25 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/fire/application/service/FireThrowAlarmUseCase.java @@ -0,0 +1,55 @@ +package com.moing.backend.domain.fire.application.service; + +import com.moing.backend.domain.member.domain.entity.Member; +import com.moing.backend.global.config.fcm.dto.request.SingleRequest; +import com.moing.backend.global.config.fcm.service.FcmService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; + +import java.util.Random; + +import static com.moing.backend.global.config.fcm.constant.FireThrowMessage.*; +import static java.lang.Math.random; + +@Service +@Transactional +@RequiredArgsConstructor +public class FireThrowAlarmUseCase { + + private final FcmService fcmService; + + public void sendFireThrowAlarm(Member throwMember, Member receiveMember) { + + Random random = new Random(System.currentTimeMillis()); + String title = getTitle(throwMember.getNickName(), receiveMember.getNickName(), random.nextInt(2)); + + String message = getMessage(throwMember.getNickName(), receiveMember.getNickName(), random.nextInt(2)); + SingleRequest singleRequest = new SingleRequest(receiveMember.getFcmToken(), title, message); + + fcmService.sendSingleDevice(singleRequest); + } + + public String getMessage(String pusher, String receiver, int num) { + + switch (num) { + case 0: return pusher + "님이" + receiver + NEW_FIRE_THROW_MESSAGE1.getMessage(); + case 1: return receiver + "님! " + pusher + NEW_FIRE_THROW_MESSAGE2.getMessage(); + } + return pusher + "님이" + receiver + NEW_FIRE_THROW_MESSAGE1.getMessage(); + } + + public String getTitle(String pusher, String receiver, int num) { + + switch (num) { + case 0: + return NEW_FIRE_THROW_TITLE1.getMessage(); + case 1: + return NEW_FIRE_THROW_TITLE2.getMessage(); + } + return NEW_FIRE_THROW_TITLE1.getMessage(); + } + + +} diff --git a/src/main/java/com/moing/backend/domain/fire/application/service/FireThrowUseCase.java b/src/main/java/com/moing/backend/domain/fire/application/service/FireThrowUseCase.java index 36d5175f..1f07c6fe 100644 --- a/src/main/java/com/moing/backend/domain/fire/application/service/FireThrowUseCase.java +++ b/src/main/java/com/moing/backend/domain/fire/application/service/FireThrowUseCase.java @@ -4,18 +4,21 @@ import com.moing.backend.domain.fire.application.dto.res.FireThrowRes; import com.moing.backend.domain.fire.application.mapper.FireMapper; import com.moing.backend.domain.fire.domain.entity.Fire; -import com.moing.backend.domain.fire.domain.repository.FireCustomRepository; import com.moing.backend.domain.fire.domain.service.FireQueryService; import com.moing.backend.domain.fire.domain.service.FireSaveService; import com.moing.backend.domain.fire.exception.NoAuthThrowFireException; +import com.moing.backend.domain.member.domain.entity.Member; import com.moing.backend.domain.member.domain.service.MemberGetService; -import com.moing.backend.domain.mission.application.service.MissionCreateUseCase; +import com.moing.backend.global.config.fcm.dto.request.SingleRequest; +import com.moing.backend.global.config.fcm.service.FcmService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import static java.lang.Math.random; + @Service @Transactional @RequiredArgsConstructor @@ -25,16 +28,27 @@ public class FireThrowUseCase { public final FireQueryService fireQueryService; private final MemberGetService memberGetService; + private final FireThrowAlarmUseCase fireThrowAlarmUseCase; + public FireThrowRes createFireThrow(String userId, Long receiveMemberId) { - Long throwMemberId = memberGetService.getMemberBySocialId(userId).getMemberId(); + Member throwMember = memberGetService.getMemberBySocialId(userId); + Member receiveMember = memberGetService.getMemberByMemberId(receiveMemberId); + + // 나에게 던질 수 없음 + if (throwMember.equals(receiveMember)) { + throw new NoAuthThrowFireException(); + } - if (!fireQueryService.hasFireCreatedWithinOneHour(throwMemberId, receiveMemberId)) { + // 1시간전 불 던진 기록이 있다면, 던질 수 없음 + if (!fireQueryService.hasFireCreatedWithinOneHour(throwMember.getMemberId(), receiveMemberId)) { throw new NoAuthThrowFireException(); } + fireThrowAlarmUseCase.sendFireThrowAlarm(throwMember, receiveMember); + return FireMapper.mapToFireThrowRes(fireSaveService.save(Fire.builder() - .throwMemberId(throwMemberId) + .throwMemberId(throwMember.getMemberId()) .receiveMemberId(receiveMemberId) .build())); } @@ -49,4 +63,6 @@ public List getFireReceiveList(String userId,Long teamId, Long m return fireReceiveRes; } + + } diff --git a/src/main/java/com/moing/backend/domain/member/domain/repository/MemberRepository.java b/src/main/java/com/moing/backend/domain/member/domain/repository/MemberRepository.java index 20cb1f23..5d445dfc 100644 --- a/src/main/java/com/moing/backend/domain/member/domain/repository/MemberRepository.java +++ b/src/main/java/com/moing/backend/domain/member/domain/repository/MemberRepository.java @@ -11,4 +11,6 @@ public interface MemberRepository extends JpaRepository, MemberCus Optional findByEmail(String email); + Optional findByMemberId(Long id); + } diff --git a/src/main/java/com/moing/backend/domain/member/domain/service/MemberGetService.java b/src/main/java/com/moing/backend/domain/member/domain/service/MemberGetService.java index dc88284d..015ca6ea 100644 --- a/src/main/java/com/moing/backend/domain/member/domain/service/MemberGetService.java +++ b/src/main/java/com/moing/backend/domain/member/domain/service/MemberGetService.java @@ -17,4 +17,8 @@ public class MemberGetService { public Member getMemberBySocialId(String socialId){ return memberRepository.findBySocialId(socialId).orElseThrow(()->new NotFoundBySocialIdException()); } + + public Member getMemberByMemberId(Long memberId) { + return memberRepository.findByMemberId(memberId).orElseThrow(()->new NotFoundBySocialIdException()); + } } diff --git a/src/main/java/com/moing/backend/domain/mission/application/service/MissionCheckScheduler.java b/src/main/java/com/moing/backend/domain/mission/application/service/MissionCheckScheduler.java deleted file mode 100644 index 824407a3..00000000 --- a/src/main/java/com/moing/backend/domain/mission/application/service/MissionCheckScheduler.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.moing.backend.domain.mission.application.service; - -public class MissionCheckScheduler { -} diff --git a/src/main/java/com/moing/backend/domain/mission/application/service/MissionCreateUseCase.java b/src/main/java/com/moing/backend/domain/mission/application/service/MissionCreateUseCase.java index c5a433b4..5de9b428 100644 --- a/src/main/java/com/moing/backend/domain/mission/application/service/MissionCreateUseCase.java +++ b/src/main/java/com/moing/backend/domain/mission/application/service/MissionCreateUseCase.java @@ -17,6 +17,9 @@ import com.moing.backend.domain.team.domain.entity.Team; import com.moing.backend.domain.team.domain.repository.TeamRepository; import com.moing.backend.domain.team.domain.service.TeamGetService; +import com.moing.backend.global.config.fcm.dto.request.MultiRequest; +import com.moing.backend.global.config.fcm.dto.request.SingleRequest; +import com.moing.backend.global.config.fcm.service.FcmService; import com.moing.backend.global.util.SecurityUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -31,6 +34,7 @@ public class MissionCreateUseCase { private final MissionQueryService missionQueryService; private final TeamGetService teamGetService; private final MemberGetService memberGetService; + private final SendMissionCreateAlarmUseCase sendMissionCreateAlarmUseCase; public MissionCreateRes createMission(String userSocialId, Long teamId, MissionReq missionReq) { @@ -39,7 +43,7 @@ public MissionCreateRes createMission(String userSocialId, Long teamId, MissionR // 소모임 장 확인 if (member.getMemberId().equals(team.getLeaderId())) { - Mission mission = MissionMapper.mapToMission(missionReq, member, MissionStatus.ONGOING); + Mission mission = MissionMapper.mapToMission(missionReq, member, MissionStatus.WAIT); // teamRepository 변경 예정 if (mission.getType() == MissionType.REPEAT && missionQueryService.isAbleCreateRepeatMission(team.getTeamId())) { @@ -47,6 +51,12 @@ public MissionCreateRes createMission(String userSocialId, Long teamId, MissionR } mission.setTeam(team); missionSaveService.save(mission); + + // 단일 미션 최초 1회 알림 + if (mission.getType().equals(MissionType.ONCE)) { + sendMissionCreateAlarmUseCase.sendNewMissionUploadAlarm(member, mission); + } + return MissionMapper.mapToMissionCreateRes(mission); } else{ @@ -54,8 +64,6 @@ public MissionCreateRes createMission(String userSocialId, Long teamId, MissionR } - - } public MissionRecommendRes getCategoryByTeam(Long teamId) { diff --git a/src/main/java/com/moing/backend/domain/mission/application/service/MissionRemindAlarmUseCase.java b/src/main/java/com/moing/backend/domain/mission/application/service/MissionRemindAlarmUseCase.java new file mode 100644 index 00000000..61fb3d1e --- /dev/null +++ b/src/main/java/com/moing/backend/domain/mission/application/service/MissionRemindAlarmUseCase.java @@ -0,0 +1,62 @@ +package com.moing.backend.domain.mission.application.service; + +import com.moing.backend.domain.mission.domain.entity.Mission; +import com.moing.backend.global.config.fcm.dto.request.MultiRequest; +import com.moing.backend.global.config.fcm.service.FcmService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; + +import java.util.Random; + +import static com.moing.backend.global.config.fcm.constant.RemindMissionTitle.*; +import static java.lang.Math.random; + +@Service +@Transactional +@RequiredArgsConstructor +public class MissionRemindAlarmUseCase { + + private final FcmService fcmService; + + public void sendRemindMissionAlarm(Mission mission) { + + Random random = new Random(System.currentTimeMillis()); + + String title = getTitle("receiver",random.nextInt(4)); + String message = getMessage(mission.getTitle(),random.nextInt(4)); + + MultiRequest multiRequest = new MultiRequest(); + } + + public String getTitle(String receiver, int num) { + switch (num) { + case 0: + return receiver + REMIND_MISSION_TITLE1.getMessage(); + case 1: + return receiver + REMIND_MISSION_TITLE2.getMessage(); + case 2: + return receiver + REMIND_MISSION_TITLE3.getMessage(); + case 3: + return receiver + REMIND_MISSION_TITLE4.getMessage(); + } + return receiver + REMIND_MISSION_TITLE4.getMessage(); + + } + public String getMessage(String missionTitle, int num) { + switch (num) { + case 0: + return missionTitle + REMIND_MISSION_MESSAGE1.getMessage(); + case 1: + return missionTitle + REMIND_MISSION_MESSAGE2.getMessage(); + case 2: + return missionTitle + REMIND_MISSION_MESSAGE3.getMessage(); + case 3: + return missionTitle + REMIND_MISSION_MESSAGE4.getMessage(); + } + return missionTitle + REMIND_MISSION_MESSAGE4.getMessage(); + + } + +} diff --git a/src/main/java/com/moing/backend/domain/mission/application/service/MissionStateTerminateBatchConfig.java b/src/main/java/com/moing/backend/domain/mission/application/service/MissionStateTerminateBatchConfig.java new file mode 100644 index 00000000..4e4885e2 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/mission/application/service/MissionStateTerminateBatchConfig.java @@ -0,0 +1,96 @@ +//package com.moing.backend.domain.mission.application.service; +// +//import com.moing.backend.domain.mission.domain.entity.Mission; +//import com.moing.backend.domain.missionState.domain.entity.MissionState; +//import com.moing.backend.domain.missionState.domain.service.MissionStateDeleteService; +//import com.moing.backend.domain.teamScore.application.service.TeamScoreLogicUseCase; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.batch.core.Job; +//import org.springframework.batch.core.Step; +//import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +//import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +//import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +//import org.springframework.batch.core.configuration.annotation.StepScope; +//import org.springframework.batch.item.ItemProcessor; +//import org.springframework.batch.item.database.JpaItemWriter; +//import org.springframework.batch.item.database.JpaPagingItemReader; +//import org.springframework.batch.item.database.builder.JpaItemWriterBuilder; +//import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +//import javax.persistence.EntityManagerFactory; +//import java.util.HashMap; +//import java.util.Map; +// +//@Slf4j +//@Configuration +//@EnableBatchProcessing +//@RequiredArgsConstructor +//public class MissionStateTerminateBatchConfig { +// +// private final JobBuilderFactory jobBuilderFactory; +// private final StepBuilderFactory stepBuilderFactory; +// private final EntityManagerFactory entityManagerFactory; +// +// private final TeamScoreLogicUseCase teamScoreLogicUseCase; +// private final MissionStateDeleteService missionStateDeleteService; +// +// @Bean +// public Job missionTerminateJob() { +// return jobBuilderFactory.get("missionTerminateJob") +// .start(missionTerminateStep()) +// .build(); +// } +// +// @Bean +// public Step missionTerminateStep() { +// return stepBuilderFactory.get("missionTerminateStep") +// .chunk(10) +// .reader(missionReader()) +// .processor(missionProcessor()) +// .writer(missionWriter()) +// .reader(missionStateReader()) +// .processor(missionStateProcessor()) +// .writer(missionStateWriter()) +// .build(); +// } +// +// @Bean +// @StepScope +// public JpaPagingItemReader missionStateReader() { +// +// Map parameterValues = new HashMap<>(); +// parameterValues.put("missionId", 10000); +// +// .parameterValues(parameterValues) // 쿼리 파라미터 설정 +// +// return new JpaPagingItemReaderBuilder() +// .name("missionStateReader") +// .entityManagerFactory(entityManagerFactory) +// .queryString("SELECT m FROM MissionState m WHERE m.mission_id = :missionId") +// .pageSize(10) +// .build(); +// } +// +// @Bean +// public ItemProcessor missionStateProcessor() { +// return mission -> { +// teamScoreLogicUseCase.updateTeamScore(mission.getId()); +// return mission; +// }; +// } +// +// +// +// @Bean +// public JpaItemWriter missionStateWriter() { +// return new JpaItemWriterBuilder() +// .entityManagerFactory(entityManagerFactory) +// .build(); +// } +// +// +// +//} diff --git a/src/main/java/com/moing/backend/domain/mission/application/service/MissionTerminateConfig.java b/src/main/java/com/moing/backend/domain/mission/application/service/MissionTerminateConfig.java new file mode 100644 index 00000000..836a5aff --- /dev/null +++ b/src/main/java/com/moing/backend/domain/mission/application/service/MissionTerminateConfig.java @@ -0,0 +1,94 @@ +//package com.moing.backend.domain.mission.application.service; +// +//import com.moing.backend.domain.mission.domain.entity.Mission; +//import com.moing.backend.domain.mission.domain.entity.constant.MissionStatus; +//import com.moing.backend.domain.mission.domain.entity.constant.MissionType; +//import com.moing.backend.domain.missionState.application.service.MissionStateScheduleUseCase; +//import com.moing.backend.domain.missionState.domain.entity.MissionState; +//import com.moing.backend.domain.missionState.domain.service.MissionStateDeleteService; +//import com.moing.backend.domain.teamScore.application.service.TeamScoreLogicUseCase; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.batch.core.Job; +//import org.springframework.batch.core.Step; +//import org.springframework.batch.core.configuration.annotation.*; +//import org.springframework.batch.item.ItemProcessor; +//import org.springframework.batch.item.database.JpaItemWriter; +//import org.springframework.batch.item.database.JpaPagingItemReader; +//import org.springframework.batch.item.database.builder.JpaItemWriterBuilder; +//import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +//import javax.persistence.EntityManagerFactory; +//import java.util.HashMap; +//import java.util.Map; +//import java.util.Optional; +// +//import static com.moing.backend.domain.mission.domain.entity.QMission.mission; +// +//@Slf4j +//@Configuration +//@EnableBatchProcessing +//@RequiredArgsConstructor +//public class MissionTerminateBatchConfig { +// +// private final JobBuilderFactory jobBuilderFactory; +// private final StepBuilderFactory stepBuilderFactory; +// private final EntityManagerFactory entityManagerFactory; +// +// private final TeamScoreLogicUseCase teamScoreLogicUseCase; +// private final MissionStateDeleteService missionStateDeleteService; +// private final MissionStateScheduleUseCase missionStateScheduleUseCase; +// +// @Bean +// public Job missionTerminateJob() { +// return jobBuilderFactory.get("missionTerminateJob") +// .start(missionTerminateStep()) +// .build(); +// } +// +// @Bean +// public Step missionTerminateStep() { +// return stepBuilderFactory.get("missionTerminateStep") +// .chunk(10) +// .reader(missionReader()) +// .processor(missionProcessor()) +// .writer(missionWriter()) +// .build(); +// } +// +// @Bean +// @StepScope +// public JpaPagingItemReader missionReader() { +// return new JpaPagingItemReaderBuilder() +// .name("missionReader") +// .entityManagerFactory(entityManagerFactory) +// .queryString("SELECT m FROM Mission m WHERE m.status = 'ONGOING' AND m.type = 'REPEAT'") +// .pageSize(10) +// .build(); +// } +// +// @Bean +// public ItemProcessor missionProcessor() { +// return mission -> { +// teamScoreLogicUseCase.updateTeamScore(mission.getId()); +// return mission; +// }; +// } +// +// +// +// @Bean +// public JpaItemWriter teamScoreWriter() { +// missionStateScheduleUseCase.missionStateReset(); +// return new JpaItemWriterBuilder() +// .entityManagerFactory(entityManagerFactory) +// .build(); +// } +// +// +// +// +// +//} diff --git a/src/main/java/com/moing/backend/domain/mission/application/service/MissionTerminationUseCase.java b/src/main/java/com/moing/backend/domain/mission/application/service/MissionTerminationUseCase.java index 18866d60..243582d5 100644 --- a/src/main/java/com/moing/backend/domain/mission/application/service/MissionTerminationUseCase.java +++ b/src/main/java/com/moing/backend/domain/mission/application/service/MissionTerminationUseCase.java @@ -33,6 +33,7 @@ public void terminateMission() { // 미션 점수 반영 -> MissionState + // MissionState 조회 해서 미션 점수 현황조회 public void getMissionScoreStatus() { diff --git a/src/main/java/com/moing/backend/domain/mission/application/service/SendMissionCreateAlarmUseCase.java b/src/main/java/com/moing/backend/domain/mission/application/service/SendMissionCreateAlarmUseCase.java new file mode 100644 index 00000000..3edc96e8 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/mission/application/service/SendMissionCreateAlarmUseCase.java @@ -0,0 +1,44 @@ +package com.moing.backend.domain.mission.application.service; + +import com.moing.backend.domain.board.domain.entity.Board; +import com.moing.backend.domain.member.domain.entity.Member; +import com.moing.backend.domain.mission.domain.entity.Mission; +import com.moing.backend.domain.team.domain.entity.Team; +import com.moing.backend.domain.teamMember.domain.service.TeamMemberGetService; +import com.moing.backend.global.config.fcm.dto.request.MultiRequest; +import com.moing.backend.global.config.fcm.service.FcmService; +import com.moing.backend.global.response.BaseServiceResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.List; +import java.util.Optional; + +import static com.moing.backend.global.config.fcm.constant.NewMissionTitle.NEW_SINGLE_MISSION_COMING; +import static com.moing.backend.global.config.fcm.constant.NewUploadTitle.UPLOAD_NOTICE_NEW_TITLE; + +@Service +@Transactional +@RequiredArgsConstructor +public class SendMissionCreateAlarmUseCase { + + private final TeamMemberGetService teamMemberGetService; + private final FcmService fcmService; + + public void sendNewMissionUploadAlarm(Member member, Mission mission) { + Team team = mission.getTeam(); + Optional> fcmTokensExceptMe = teamMemberGetService.getFcmTokensExceptMe(team.getTeamId(), member.getMemberId()); + + String title = team.getName() + " " + NEW_SINGLE_MISSION_COMING.getTitle(); + String message = mission.getTitle(); + + Optional> fcmTokens = teamMemberGetService.getFcmTokensExceptMe(team.getTeamId(), member.getMemberId()); + if (fcmTokens.isPresent() && !fcmTokens.get().isEmpty()) { + MultiRequest toMultiRequest = new MultiRequest(fcmTokens.get(), title, message); + fcmService.sendMultipleDevices(toMultiRequest); + + } + } +} + diff --git a/src/main/java/com/moing/backend/domain/mission/domain/entity/constant/MissionStatus.java b/src/main/java/com/moing/backend/domain/mission/domain/entity/constant/MissionStatus.java index 07007a45..55f18654 100644 --- a/src/main/java/com/moing/backend/domain/mission/domain/entity/constant/MissionStatus.java +++ b/src/main/java/com/moing/backend/domain/mission/domain/entity/constant/MissionStatus.java @@ -9,5 +9,6 @@ public enum MissionStatus { END, ONGOING, SUCCESS, - FAIL + FAIL, + WAIT } \ No newline at end of file diff --git a/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepository.java b/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepository.java index 45b842f0..4ba36e8a 100644 --- a/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepository.java +++ b/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepository.java @@ -21,6 +21,8 @@ public interface MissionCustomRepository { Optional> findOngoingRepeatMissions(); + Optional> findRepeatMissionByStatus(MissionStatus missionStatus); + boolean findRepeatMissionsByTeamId(Long teamId); diff --git a/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepositoryImpl.java b/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepositoryImpl.java index 0511444d..79691e0e 100644 --- a/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepositoryImpl.java +++ b/src/main/java/com/moing/backend/domain/mission/domain/repository/MissionCustomRepositoryImpl.java @@ -84,6 +84,17 @@ public Optional> findOngoingRepeatMissions() { .fetch()); } + @Override + public Optional> findRepeatMissionByStatus(MissionStatus missionStatus) { + return Optional.ofNullable(queryFactory + .select(mission) + .from(mission) + .where(mission.status.eq(missionStatus), + mission.type.eq(MissionType.REPEAT)) + .fetch()); + } + + @Override public Optional> findSingleMissionByMemberId(Long memberId, List teams) { return Optional.ofNullable(queryFactory diff --git a/src/main/java/com/moing/backend/domain/mission/domain/service/MissionQueryService.java b/src/main/java/com/moing/backend/domain/mission/domain/service/MissionQueryService.java index 3190e252..ef0a716b 100644 --- a/src/main/java/com/moing/backend/domain/mission/domain/service/MissionQueryService.java +++ b/src/main/java/com/moing/backend/domain/mission/domain/service/MissionQueryService.java @@ -70,4 +70,9 @@ public List findOngoingRepeatMissions() { public boolean isAbleCreateRepeatMission(Long teamId) { return missionRepository.findRepeatMissionsByTeamId(teamId); } + + public List findMissionByStatus(MissionStatus missionStatus) { + return missionRepository.findRepeatMissionByStatus(missionStatus).orElseThrow(NotFoundMissionException::new + ); + } } diff --git a/src/main/java/com/moing/backend/domain/missionArchive/application/service/MissionArchiveCreateUseCase.java b/src/main/java/com/moing/backend/domain/missionArchive/application/service/MissionArchiveCreateUseCase.java index c66694a3..5a1463fd 100644 --- a/src/main/java/com/moing/backend/domain/missionArchive/application/service/MissionArchiveCreateUseCase.java +++ b/src/main/java/com/moing/backend/domain/missionArchive/application/service/MissionArchiveCreateUseCase.java @@ -15,6 +15,7 @@ import com.moing.backend.domain.missionArchive.domain.service.MissionArchiveQueryService; import com.moing.backend.domain.missionArchive.domain.service.MissionArchiveSaveService; import com.moing.backend.domain.missionArchive.exception.NoMoreMissionArchiveException; +import com.moing.backend.domain.missionArchive.exception.NotYetMissionArchiveException; import com.moing.backend.domain.missionState.application.service.MissionStateUseCase; import com.moing.backend.domain.missionState.domain.service.MissionStateSaveService; import com.moing.backend.domain.missionHeart.domain.service.MissionHeartQueryService; @@ -52,12 +53,21 @@ public MissionArchiveRes createArchive(String userSocialId, Long missionId, Miss MissionArchive newArchive = MissionArchiveMapper.mapToMissionArchive(missionReq, member, mission); + // 단일 미션인 경우 미션 인증 시도 시 ongoing 으로 변경 + if (mission.getType() == MissionType.ONCE && mission.getStatus() == MissionStatus.WAIT) { + mission.updateStatus(MissionStatus.ONGOING); + } // 인증 완료한 미션인지 확인 if (isDoneMission(memberId,mission)) { throw new NoMoreMissionArchiveException(); } + // 반복 미션일 경우 if (mission.getType() == MissionType.REPEAT) { + // 예정된 반복미션 접근 제한 + if (mission.getStatus() == MissionStatus.WAIT) { + throw new NotYetMissionArchiveException(); + } // 당일 1회 인증만 가능 if(!missionArchiveQueryService.findDoneTodayArchive(memberId,missionId)) newArchive.updateCount(missionArchiveQueryService.findMyDoneArchives(memberId, missionId)+1); diff --git a/src/main/java/com/moing/backend/domain/missionArchive/exception/NotYetMissionArchiveException.java b/src/main/java/com/moing/backend/domain/missionArchive/exception/NotYetMissionArchiveException.java new file mode 100644 index 00000000..25c87401 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/missionArchive/exception/NotYetMissionArchiveException.java @@ -0,0 +1,12 @@ +package com.moing.backend.domain.missionArchive.exception; + +import com.moing.backend.global.response.ErrorCode; +import org.springframework.http.HttpStatus; + +public class NotYetMissionArchiveException extends MissionArchiveException { + + public NotYetMissionArchiveException() { + super(ErrorCode.NOT_YET_MISSION_ARCHIVE, + HttpStatus.NOT_FOUND); + } +} diff --git a/src/main/java/com/moing/backend/domain/missionHeart/application/service/MissionHeartUseCase.java b/src/main/java/com/moing/backend/domain/missionHeart/application/service/MissionHeartUseCase.java index 86304259..1b296ce7 100644 --- a/src/main/java/com/moing/backend/domain/missionHeart/application/service/MissionHeartUseCase.java +++ b/src/main/java/com/moing/backend/domain/missionHeart/application/service/MissionHeartUseCase.java @@ -10,6 +10,7 @@ import com.moing.backend.domain.missionHeart.domain.service.MissionHeartQueryService; import com.moing.backend.domain.missionHeart.domain.service.MissionHeartSaveService; import com.moing.backend.domain.missionHeart.domain.service.MissionHeartUpdateService; +import com.moing.backend.domain.missionHeart.exception.NoAccessMissionHeartException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,6 +33,9 @@ public MissionHeartRes pushHeart(String socialId,Long archiveId, String status) MissionArchive archive = missionArchiveQueryService.findByMissionArchiveId(archiveId); MissionHeart missionHeart = MissionHeartMapper.mapToMissionHeart(memberId, archive, MissionHeartStatus.valueOf(status)); + if (memberId.equals(missionHeart.getMissionArchive().getMember().getMemberId())){ + throw new NoAccessMissionHeartException(); + } if(missionHeartQueryService.isAlreadyHeart(memberId, archiveId)) { return MissionHeartMapper.mapToMissionHeartRes( missionHeartUpdateService.update(missionHeart)); diff --git a/src/main/java/com/moing/backend/domain/missionHeart/exception/MissionHeartException.java b/src/main/java/com/moing/backend/domain/missionHeart/exception/MissionHeartException.java new file mode 100644 index 00000000..f085efe1 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/missionHeart/exception/MissionHeartException.java @@ -0,0 +1,11 @@ +package com.moing.backend.domain.missionHeart.exception; + +import com.moing.backend.global.exception.ApplicationException; +import com.moing.backend.global.response.ErrorCode; +import org.springframework.http.HttpStatus; + +public abstract class MissionHeartException extends ApplicationException { + protected MissionHeartException(ErrorCode errorCode, HttpStatus httpStatus) { + super(errorCode, httpStatus); + } +} diff --git a/src/main/java/com/moing/backend/domain/missionHeart/exception/NoAccessMissionHeartException.java b/src/main/java/com/moing/backend/domain/missionHeart/exception/NoAccessMissionHeartException.java new file mode 100644 index 00000000..01fdcde8 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/missionHeart/exception/NoAccessMissionHeartException.java @@ -0,0 +1,11 @@ +package com.moing.backend.domain.missionHeart.exception; + +import com.moing.backend.global.response.ErrorCode; +import org.springframework.http.HttpStatus; + +public class NoAccessMissionHeartException extends MissionHeartException { + public NoAccessMissionHeartException() { + super(ErrorCode.NO_ACCESS_HEART_FOR_ME, HttpStatus.NOT_FOUND); + + } +} diff --git a/src/main/java/com/moing/backend/domain/missionState/application/service/MissionStateScheduleUseCase.java b/src/main/java/com/moing/backend/domain/missionState/application/service/MissionStateScheduleUseCase.java index 833f3a4c..15646366 100644 --- a/src/main/java/com/moing/backend/domain/missionState/application/service/MissionStateScheduleUseCase.java +++ b/src/main/java/com/moing/backend/domain/missionState/application/service/MissionStateScheduleUseCase.java @@ -9,6 +9,8 @@ import com.moing.backend.domain.teamScore.application.service.TeamScoreLogicUseCase; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,6 +20,7 @@ @Slf4j @Service @Transactional +@EnableScheduling // 스케줄링 활성화 @RequiredArgsConstructor public class MissionStateScheduleUseCase { @@ -28,7 +31,11 @@ public class MissionStateScheduleUseCase { private final TeamScoreLogicUseCase teamScoreLogicUseCase; - // 스케쥴러에 쓰면 됨. + /** + * 반복미션 마감 + * 일요일 마감 루틴 + */ + @Scheduled(cron = "0 59 23 * * SUN") public void sundayRepeatMissionRoutine() { // 모든 진행중인 반복 미션 모아서 @@ -41,27 +48,59 @@ public void sundayRepeatMissionRoutine() { // 미션 state 에서 지우기 ( 이것도 대용량 ) missionStateReset(ongoingRepeatMissions); } + public void missionStateReset(List missionIds) { List missionStates = missionStateQueryService.findByMissionId(missionIds); missionStateDeleteService.deleteMissionState(missionStates); } - // 한시간마다 실행 - // 미션 단위 - public void singleMissionEndRoutineByMission() { - Mission mission = new Mission(); +// /** +// * 단일 미션 마감 +// * 미션 단위 마감 +// * 한시간 마다 실행 +// */ +// public void singleMissionEndRoutineByMission() { +// +// Mission mission = new Mission(); +// LocalDateTime now = LocalDateTime.now(); +// +// if (mission.getDueTo().isAfter(now)) { +// mission.updateStatus(MissionStatus.END); +// teamScoreLogicUseCase.updateTeamScore(mission.getId()); +// } +// } + + /** + * 단일 미션 마감 + * 해당 시간 미션 마감 + * 한시간 마다 실행 + */ + @Scheduled(cron = "0 0 * * * *") + public void singleMissionEndRoutine() { LocalDateTime now = LocalDateTime.now(); - if (mission.getDueTo().isAfter(now)) { + List missionByDueTo = missionQueryService.findMissionByDueTo(); + + missionByDueTo.stream().forEach(mission -> { mission.updateStatus(MissionStatus.END); - } - } + teamScoreLogicUseCase.updateTeamScore(mission.getId()); + }); - public void singleMissionEndRoutine() { + } + /** + * 미션 시작 + * 월요일 아침 + */ + @Scheduled(cron = "0 0 0 * * MON") + public void RepeatMissionStart() { + List startMission = missionQueryService.findMissionByStatus(MissionStatus.WAIT); + startMission.stream().forEach( + mission -> mission.updateStatus(MissionStatus.ONGOING) + ); } } diff --git a/src/main/java/com/moing/backend/domain/team/application/service/CreateTeamUserCase.java b/src/main/java/com/moing/backend/domain/team/application/service/CreateTeamUserCase.java index 6513c478..262e7a8b 100644 --- a/src/main/java/com/moing/backend/domain/team/application/service/CreateTeamUserCase.java +++ b/src/main/java/com/moing/backend/domain/team/application/service/CreateTeamUserCase.java @@ -8,6 +8,8 @@ import com.moing.backend.domain.team.domain.entity.Team; import com.moing.backend.domain.team.domain.service.TeamSaveService; import com.moing.backend.domain.teamMember.domain.service.TeamMemberSaveService; +import com.moing.backend.domain.teamScore.application.mapper.TeamScoreMapper; +import com.moing.backend.domain.teamScore.domain.service.TeamScoreSaveService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -22,6 +24,8 @@ public class CreateTeamUserCase { private final TeamSaveService teamSaveService; private final TeamMemberSaveService teamMemberSaveService; private final TeamMapper teamMapper; + private final TeamScoreSaveService teamScoreSaveService; + private final TeamScoreMapper teamScoreMapper; public CreateTeamResponse createTeam(CreateTeamRequest createTeamRequest, String socialId){ Member member = memberGetService.getMemberBySocialId(socialId); @@ -31,6 +35,7 @@ public CreateTeamResponse createTeam(CreateTeamRequest createTeamRequest, String //====지워야 함 (테스트 용)===== team.approveTeam(); //====지워야 함 (테스트 용)===== + teamScoreSaveService.save(teamScoreMapper.mapToTeamScore(team)); // 팀스코어 엔티티 생성 return new CreateTeamResponse(team.getTeamId()); } } diff --git a/src/main/java/com/moing/backend/domain/teamScore/application/mapper/TeamScoreMapper.java b/src/main/java/com/moing/backend/domain/teamScore/application/mapper/TeamScoreMapper.java new file mode 100644 index 00000000..49031bc2 --- /dev/null +++ b/src/main/java/com/moing/backend/domain/teamScore/application/mapper/TeamScoreMapper.java @@ -0,0 +1,19 @@ +package com.moing.backend.domain.teamScore.application.mapper; + +import com.moing.backend.domain.team.domain.entity.Team; +import com.moing.backend.domain.teamScore.domain.entity.TeamScore; +import com.moing.backend.global.annotation.Mapper; +import org.springframework.stereotype.Component; + +@Component +public class TeamScoreMapper { + + public TeamScore mapToTeamScore(Team team) { + return TeamScore.builder() + .score(0L) + .level(1L) + .team(team) + .build(); + } + +} diff --git a/src/main/java/com/moing/backend/domain/teamScore/domain/entity/TeamScore.java b/src/main/java/com/moing/backend/domain/teamScore/domain/entity/TeamScore.java index a37f311e..6fd0fb49 100644 --- a/src/main/java/com/moing/backend/domain/teamScore/domain/entity/TeamScore.java +++ b/src/main/java/com/moing/backend/domain/teamScore/domain/entity/TeamScore.java @@ -41,7 +41,7 @@ public void levelUp() { // 0부터 시작하기 때문에 무조건 0에서 걸림. for (int i = 5; i > 0; i--) { - if (steps[i] <= this.level && this.level <= steps[i - 1]) { + if (steps[i-1] <= this.level && this.level <= steps[i]) { if (20 + (i-1) * 15 <= score) { // 여길 들어가질 않음 this.level+=1; this.score -= (20 + (i-1) * 15); diff --git a/src/main/java/com/moing/backend/global/config/fcm/constant/FireThrowMessage.java b/src/main/java/com/moing/backend/global/config/fcm/constant/FireThrowMessage.java new file mode 100644 index 00000000..dbfb2f79 --- /dev/null +++ b/src/main/java/com/moing/backend/global/config/fcm/constant/FireThrowMessage.java @@ -0,0 +1,23 @@ +package com.moing.backend.global.config.fcm.constant; + + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum FireThrowMessage { + + NEW_FIRE_THROW_TITLE1("어라… 왜 이렇게 발등이 뜨겁지?\uD83E\uDD28"), + NEW_FIRE_THROW_TITLE2("⚠\uFE0F불조심⚠\uFE0F "), + + + NEW_FIRE_THROW_MESSAGE1("님에게 불을 던졌어요! 어서 미션을 인증해볼까요?"), + NEW_FIRE_THROW_MESSAGE2("님이 던진 불에 타버릴지도 몰라요! 어서 인증하러갈까요?"); + + + + private final String message; + + +} diff --git a/src/main/java/com/moing/backend/global/config/fcm/constant/NewMissionTitle.java b/src/main/java/com/moing/backend/global/config/fcm/constant/NewMissionTitle.java new file mode 100644 index 00000000..9bb30c20 --- /dev/null +++ b/src/main/java/com/moing/backend/global/config/fcm/constant/NewMissionTitle.java @@ -0,0 +1,13 @@ +package com.moing.backend.global.config.fcm.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum NewMissionTitle { + + NEW_SINGLE_MISSION_COMING("의 새로운 미션이 등장했어요! "), + NEW_REPEAT_MISSION_COMING("의 새로운 반복미션이 시작되었어요! "); + private final String title; +} diff --git a/src/main/java/com/moing/backend/global/config/fcm/constant/RemindMissionTitle.java b/src/main/java/com/moing/backend/global/config/fcm/constant/RemindMissionTitle.java new file mode 100644 index 00000000..d387ca59 --- /dev/null +++ b/src/main/java/com/moing/backend/global/config/fcm/constant/RemindMissionTitle.java @@ -0,0 +1,23 @@ +package com.moing.backend.global.config.fcm.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum RemindMissionTitle { + + REMIND_MISSION_TITLE1("님, 오늘도 좋은하루!"), + REMIND_MISSION_TITLE2("님, 잊으신 건 아니죠?\uD83D\uDC40"), + REMIND_MISSION_TITLE3("님, 좋은 아침이에요! ☀\uFE0F"), + REMIND_MISSION_TITLE4("오늘의 열정이 타오르불\uD83D\uDD25"), + + + REMIND_MISSION_MESSAGE1(" 도전과 함께⚡\uFE0F "), + REMIND_MISSION_MESSAGE2(" 인증을 모임원들이 기다리고 있어요!"), + REMIND_MISSION_MESSAGE3(" 수행하고 이번주도 도전해요👊 "), + REMIND_MISSION_MESSAGE4(" 도전하고 성취감 뿜뿜💪 "); + + private final String message; + + } diff --git a/src/main/java/com/moing/backend/global/response/ErrorCode.java b/src/main/java/com/moing/backend/global/response/ErrorCode.java index 28fe9bd0..71d070ba 100644 --- a/src/main/java/com/moing/backend/global/response/ErrorCode.java +++ b/src/main/java/com/moing/backend/global/response/ErrorCode.java @@ -31,6 +31,7 @@ public enum ErrorCode { NOT_FOUND_END_MISSION("M0003", "기한이 지난 미션을 찾을 수 없습니다."), NO_MORE_CREATE_MISSION("M0004", "반복미션은 3개까지 생성할 수 있습니다."), NOT_FOUND_MISSION_ARCHIVE("MA0001", "아직 미션을 제출하지 않았습니다."), + NOT_YET_MISSION_ARCHIVE("MA0001", "아직 미션을 제출할 수 없습니다."), NO_MORE_ARCHIVE_ERROR("MA0001", "지정한 횟수 이상 미션을 인증할 수 없습니다."), //불던지기 관련 에러 코드 @@ -38,6 +39,8 @@ public enum ErrorCode { NOT_FOUND_FIRE_RECEIVERS("F001","불던지기를 받을 사람을 찾을 수 없습니다"), NOT_AUTH_FIRE_THROW("F002","1시간 이내에 불던지기를 할 수 없습니다"), + NO_ACCESS_HEART_FOR_ME("MH001", "나에게 좋아요를 누를 수 없습니다"), + //팀 관련 에러 코드 NOT_FOUND_BY_TEAM_ID_ERROR("T0001", "해당 teamId인 팀이 존재하지 않습니다."), NOT_AUTH_BY_TEAM_ERROR("T0002","권한이 없습니다."),