Skip to content

Commit

Permalink
feat: 미션 마지막 날 푸시 알림 (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimyu0218 authored Dec 10, 2024
1 parent 1fda708 commit b1d7dc2
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,19 @@ public void sendGroupData(final String title, final String body, final String to
private Data makeData(final String title, final String body, final Long missionId) {
return new Data(title, body, missionId);
}

public void sendNotificationWithData(String title, String body, String topic) {
Notification notification = makeNotification(title, body);
Message message = Message.builder()
.setNotification(notification)
.putData("missionId", TopicGenerator.extractIdentifier(topic))
.setTopic(topic)
.build();

try {
FirebaseMessaging.getInstance().send(message);
} catch (FirebaseMessagingException e) {
throw new BaseException(ErrorCode.FAILED_TO_SEND_GROUP_MESSAGE, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ public class TopicGenerator {
public static String getTopic(Long missionId) {
return TOPIC_PREFIX + missionId;
}

public static String extractIdentifier(String topic) {
return topic.replace(TOPIC_PREFIX, "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.nexters.goalpanzi.application.mission.dto.request.ViewMissionVerificationCommand;
import com.nexters.goalpanzi.application.mission.dto.response.MissionVerificationResponse;
import com.nexters.goalpanzi.application.mission.dto.response.MissionVerificationsResponse;
import com.nexters.goalpanzi.application.mission.event.CompleteMissionEvent;
import com.nexters.goalpanzi.application.upload.ObjectStorageClient;
import com.nexters.goalpanzi.common.annotation.RedissonLock;
import com.nexters.goalpanzi.domain.common.BaseEntity;
Expand All @@ -22,6 +23,7 @@
import com.nexters.goalpanzi.exception.NotFoundException;
import com.nexters.goalpanzi.infrastructure.firebase.PushMessageSender;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -46,6 +48,7 @@ public class MissionVerificationService {

private final ObjectStorageClient objectStorageClient;
private final PushMessageSender pushMessageSender;
private final ApplicationEventPublisher applicationEventPublisher;

private final MissionVerificationValidator missionVerificationValidator;
private final MissionVerificationResponseSorter missionVerificationResponseSorter;
Expand All @@ -70,13 +73,26 @@ public MissionVerificationResponse getMyVerification(final MyMissionVerification
@RedissonLock("MissionVerification")
@Transactional
public void createVerification(final CreateMissionVerificationCommand command) {
MissionMember missionMember = missionMemberRepository.getMissionMember(command.memberId(), command.missionId());
MissionMember missionMember = missionMemberRepository.getMissionMemberWithMemberAndMission(command.memberId(), command.missionId());

missionVerificationValidator.validate(missionMember);

String imageUrl = objectStorageClient.uploadFile(command.imageFile());
missionMember.verify();
missionVerificationRepository.save(new MissionVerification(missionMember.getMember(), missionMember.getMission(), imageUrl, missionMember.getVerificationCount()));

Mission mission = missionMember.getMission();
if (mission.isEndDate(LocalDate.now()) && isFirstPlace(command.missionId(), missionMember.getMember())) {
applicationEventPublisher.publishEvent(
new CompleteMissionEvent(command.missionId())
);
}
}

private boolean isFirstPlace(final Long missionId, final Member member) {
List<MissionMember> missionMembers = missionMemberRepository.findAllByMissionId(missionId);
int rank = MemberRanks.from(missionMembers).getRankByMember(member).rank();
return rank == 1;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.nexters.goalpanzi.application.mission.event;

public record CompleteMissionEvent(
Long missionId,
String nickname
Long missionId
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public enum PushMessage {
MISSION_VERIFICATION_WARNING("\u23F0 마감임박! 1시간 남았어요!\uD83E\uDDE8\uD83D\uDCA5", "지금 인증 안 하면 오늘은 인증 실패!ㅠㅠ"),
MISSION_VERIFIED("˗ˋˏ 와 ˎˊ˗ %d명이 벌써 인증 완료 ˗ˋˏ 와 ˎˊ˗ ", "지금 누가 앞서가는지 확인해볼까요?"),
MISSION_NO_ONE_VERIFIED("잊었니?..\uD83C\uDF42", "아직 아무도 인증 안 했어요! 1빠로 인증해 모두를 앞서갈 타이밍!"),
MISSION_COMPLETED("아니 글쎄..걔가 결국 1등 했다고?! \uD83D\uDDEF\uFE0F", "첫 번째 미션 완수자 등장! 빠르게 확인해 보세요!"),
MISSION_COMPLETED("아니 글쎄.. 걔가 1등 했다고?! \uD83D\uDDEF\uFE0F", "현재 이 친구가 1등이에요! 누군지 빠르게 확인해 보세요!"),
MISSION_DELETED("뭐? 미션 끝났다고? 너 누군데? \uD83D\uDC40", "방장이 미션을 끝냈어요! 다음 미션에서 새롭게 만나요!"),

// 미션 종료 후
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/nexters/goalpanzi/domain/mission/Mission.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ public boolean isVerificationStatusPushTime(final int hour) {
return hour == PushTime.EVERYDAY.getHour();
}

/**
* <b>오늘 일자가 미션 마지막 날인지 검증</b>
*
* @param today 오늘 일자
* @return 미션 마지막 날 여부
*/
public boolean isEndDate(final LocalDate today) {
return today.isEqual(missionEndDate.toLocalDate());
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
public interface MissionMemberRepository extends JpaRepository<MissionMember, Long> {
Optional<MissionMember> findByMemberIdAndMissionId(final Long memberId, final Long missionId);

@Query("SELECT mm FROM MissionMember mm"
+ " JOIN FETCH mm.member JOIN FETCH mm.mission"
+ " WHERE mm.member.id = :memberId AND mm.mission.id = :missionId"
)
Optional<MissionMember> findWithMemberAndMissionByMemberIdAndMissionId(final Long memberId, final Long missionId);

List<MissionMember> findAllByMissionId(final Long MissionId);

List<MissionMember> findAllByMissionId(final Long missionId, Sort sort);
Expand All @@ -32,6 +38,11 @@ default MissionMember getMissionMember(final Long memberId, final Long missionId
.orElseThrow(() -> new NotFoundException(ErrorCode.NOT_JOINED_MISSION_MEMBER));
}

default MissionMember getMissionMemberWithMemberAndMission(final Long memberId, final Long missionId) {
return findWithMemberAndMissionByMemberIdAndMissionId(memberId, missionId)
.orElseThrow(() -> new NotFoundException(ErrorCode.NOT_JOINED_MISSION_MEMBER));
}

default long getDaysAfterMissionCompletion(final Long memberId) {
Optional<MissionMember> missionMember = findTop1ByMemberIdOrderByUpdatedAtDesc(memberId);
if (missionMember.isEmpty() || !missionMember.get().isCompleted()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface PushMessageSender {
void sendIndividualData(final String title, final String body, final String token, final Long missionId);

void sendGroupData(final String title, final String body, final String topic, final Long missionId);

void sendNotificationWithData(final String title, final String body, final String topic);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.nexters.goalpanzi.presentation.fcmtest;

import com.nexters.goalpanzi.application.firebase.TopicGenerator;
import com.nexters.goalpanzi.infrastructure.firebase.PushMessageSender;
import com.nexters.goalpanzi.infrastructure.firebase.TopicSubscriber;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -57,4 +58,21 @@ ResponseEntity<Void> sendData(

return ResponseEntity.ok().build();
}

@Operation
@GetMapping("notification-with-data")
ResponseEntity<Void> sendNotificationWithData(
@Schema(description = "deviceToken", requiredMode = Schema.RequiredMode.REQUIRED)
@RequestParam final String deviceToken
) {
String topic = TopicGenerator.getTopic(1L);
topicSubscriber.subscribeToTopic(List.of(deviceToken), topic);
pushMessageSender.sendNotificationWithData(
"혼합 메시지 테스트",
"notification { title: string, body: string }, data { missionId: string }",
topic
);

return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class MissionTest {
LocalDateTime.now(),
LocalDateTime.now().plusDays(30),
TimeOfDay.EVERYDAY,
List.of(DayOfWeek.FRIDAY),
WEEK,
BOARD_COUNT,
InvitationCode.generate()
);
Expand All @@ -137,7 +137,7 @@ class MissionTest {
LocalDateTime.now(),
LocalDateTime.now().plusDays(30),
timeOfDay,
List.of(DayOfWeek.FRIDAY),
WEEK,
BOARD_COUNT,
InvitationCode.generate()
);
Expand All @@ -152,7 +152,7 @@ class MissionTest {
LocalDateTime.now(),
LocalDateTime.now().plusDays(30),
TimeOfDay.MORNING,
List.of(DayOfWeek.FRIDAY),
WEEK,
BOARD_COUNT,
InvitationCode.generate()
);
Expand All @@ -167,7 +167,7 @@ class MissionTest {
LocalDateTime.now(),
LocalDateTime.now().plusDays(30),
TimeOfDay.AFTERNOON,
List.of(DayOfWeek.FRIDAY),
WEEK,
BOARD_COUNT,
InvitationCode.generate()
);
Expand All @@ -182,7 +182,7 @@ class MissionTest {
LocalDateTime.now(),
LocalDateTime.now().plusDays(30),
TimeOfDay.EVERYDAY,
List.of(DayOfWeek.FRIDAY),
WEEK,
BOARD_COUNT,
InvitationCode.generate()
);
Expand All @@ -198,7 +198,7 @@ class MissionTest {
now,
now.plusDays(30),
TimeOfDay.EVERYDAY,
List.of(DayOfWeek.FRIDAY),
WEEK,
BOARD_COUNT,
InvitationCode.generate()
);
Expand Down Expand Up @@ -229,4 +229,25 @@ class MissionTest {
() -> assertThat(mission.isVerificationWarningPushTime(uploadEndTime.minusMinutes(30))).isTrue()
);
}

@Test
void 오늘_일자가_미션_마지막_날인지_검증한다() {
LocalDateTime startDate = LocalDateTime.now();
LocalDateTime endDate = startDate.plusDays(30);
Mission mission = Mission.create(
MEMBER_ID,
DESCRIPTION,
startDate,
endDate,
TimeOfDay.EVERYDAY,
List.of(DayOfWeek.FRIDAY),
BOARD_COUNT,
InvitationCode.generate()
);

assertAll(
() -> assertThat(mission.isEndDate(endDate.toLocalDate())).isTrue(),
() -> assertThat(mission.isEndDate(endDate.minusDays(1).toLocalDate())).isFalse()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.nexters.goalpanzi.domain.mission.repository;

import com.nexters.goalpanzi.domain.member.Member;
import com.nexters.goalpanzi.domain.member.SocialType;
import com.nexters.goalpanzi.domain.member.repository.MemberRepository;
import com.nexters.goalpanzi.domain.mission.InvitationCode;
import com.nexters.goalpanzi.domain.mission.Mission;
import com.nexters.goalpanzi.domain.mission.MissionMember;
import com.nexters.goalpanzi.domain.mission.TimeOfDay;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import java.time.LocalDateTime;

import static com.nexters.goalpanzi.fixture.MemberFixture.EMAIL_HOST;
import static com.nexters.goalpanzi.fixture.MemberFixture.SOCIAL_ID;
import static com.nexters.goalpanzi.fixture.MissionFixture.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

@DataJpaTest
class MissionMemberRepositoryTest {

@Autowired
private MissionMemberRepository missionMemberRepository;

@Autowired
private MemberRepository memberRepository;

@Autowired
private MissionRepository missionRepository;

private final Member MEMBER = Member.socialLogin(SOCIAL_ID, EMAIL_HOST, SocialType.GOOGLE);

@Test
void 미션_멤버를_멤버와_미션과_함께_조회한다() {
Member member = memberRepository.save(MEMBER);
Mission mission = missionRepository.save(
Mission.create(
member.getId(),
DESCRIPTION,
LocalDateTime.now().plusDays(1),
LocalDateTime.now().plusDays(31),
TimeOfDay.EVERYDAY,
WEEK,
BOARD_COUNT,
InvitationCode.generate()
)
);
missionMemberRepository.save(MissionMember.join(member, mission));

MissionMember missionMember = missionMemberRepository.getMissionMemberWithMemberAndMission(member.getId(), mission.getId());
assertAll(
() -> assertThat(missionMember.getMember()).isEqualTo(member),
() -> assertThat(missionMember.getMission()).isEqualTo(mission)
);
}
}

0 comments on commit b1d7dc2

Please sign in to comment.