Skip to content

Commit

Permalink
feat: 친구가 당일 미션을 완료하지 않은 경우 재촉하기 (#309)
Browse files Browse the repository at this point in the history
* feat: 미션 캘린더 조회 시 오늘 미션 완료했는지 여부 UrgingStatus 필드 추가

* move: 패키지 이동

* move: PushNotificationConstants 패키지 이동

* feat: 친구가 당일 미션을 완료하지 않은 경우 재촉하기 기능 구현

* test: 미션 재촉하기 테스트 코드 작성

* style: spotless

* refactor: memberId 비교 equals로 변경

* style: 출력문 제거

* fix: Long 비교 equals로 변경

* style: spotless
  • Loading branch information
kdomo authored Feb 12, 2024
1 parent 72dc028 commit 67cf0b4
Show file tree
Hide file tree
Showing 15 changed files with 371 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.depromeet.domain.follow.application;

import static com.depromeet.domain.common.constants.PushNotificationConstants.*;
import static com.depromeet.global.common.constants.PushNotificationConstants.*;

import com.depromeet.domain.follow.dao.MemberRelationRepository;
import com.depromeet.domain.follow.domain.MemberRelation;
Expand All @@ -13,10 +13,10 @@
import com.depromeet.domain.mission.domain.Mission;
import com.depromeet.domain.missionRecord.domain.ImageUploadStatus;
import com.depromeet.domain.missionRecord.domain.MissionRecord;
import com.depromeet.domain.notification.application.FcmService;
import com.depromeet.domain.notification.dao.NotificationRepository;
import com.depromeet.domain.notification.domain.Notification;
import com.depromeet.domain.notification.domain.NotificationType;
import com.depromeet.global.config.fcm.FcmService;
import com.depromeet.global.error.exception.CustomException;
import com.depromeet.global.error.exception.ErrorCode;
import com.depromeet.global.util.MemberUtil;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.depromeet.domain.member.application;

import static com.depromeet.domain.common.constants.PushNotificationConstants.*;
import static com.depromeet.global.common.constants.PushNotificationConstants.*;

import com.depromeet.domain.auth.dao.RefreshTokenRepository;
import com.depromeet.domain.auth.dto.request.UsernameCheckRequest;
Expand All @@ -16,7 +16,7 @@
import com.depromeet.domain.member.dto.response.MemberFindOneResponse;
import com.depromeet.domain.member.dto.response.MemberSearchResponse;
import com.depromeet.domain.member.dto.response.MemberSocialInfoResponse;
import com.depromeet.global.config.fcm.FcmService;
import com.depromeet.domain.notification.application.FcmService;
import com.depromeet.global.error.exception.CustomException;
import com.depromeet.global.error.exception.ErrorCode;
import com.depromeet.global.util.MemberUtil;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/depromeet/domain/mission/domain/Mission.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,15 @@ public void updateMission(String name, String content, MissionVisibility visibil
this.content = content;
this.visibility = visibility;
}

public boolean isCompletedMissionToday() {
return this.getMissionRecords().stream()
.filter(
record ->
record.getStartedAt()
.toLocalDate()
.equals(LocalDateTime.now().toLocalDate()))
.findFirst()
.isPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,28 @@ public MissionRecordFindOneResponse findOneMissionRecord(Long recordId) {

@Transactional(readOnly = true)
public MissionRecordCalendarResponse findAllMissionRecord(Long missionId, YearMonth yearMonth) {
final Member member = memberUtil.getCurrentMember();
List<MissionRecord> missionRecords =
missionRecordRepository.findAllByMissionIdAndYearMonth(missionId, yearMonth);
List<MissionRecordFindResponse> missionRecordFindResponses =
missionRecords.stream().map(MissionRecordFindResponse::from).toList();
Mission mission = findMissionById(missionId);

UrgingStatus urgingStatus = getUrgingStatus(mission, member);

return MissionRecordCalendarResponse.of(
mission.getStartedAt(), mission.getFinishedAt(), missionRecordFindResponses);
mission.getStartedAt(),
mission.getFinishedAt(),
missionRecordFindResponses,
urgingStatus);
}

private UrgingStatus getUrgingStatus(Mission mission, Member member) {
if (member.getId().equals(mission.getMember().getId())
|| mission.isCompletedMissionToday()) {
return UrgingStatus.NONE;
}
return UrgingStatus.URGING;
}

public MissionRecordUpdateResponse updateMissionRecord(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ public record MissionRecordCalendarResponse(
defaultValue = "2024-01-15 00:34:00",
type = "string")
LocalDateTime missionFinishedAt,
@Schema(description = "미션 기록들") List<MissionRecordFindResponse> missionRecords) {
@Schema(description = "미션 기록들") List<MissionRecordFindResponse> missionRecords,
@Schema(description = "재촉하기 여부") UrgingStatus urgingStatus) {
public static MissionRecordCalendarResponse of(
LocalDateTime missionStartedAt,
LocalDateTime missionFinishedAt,
List<MissionRecordFindResponse> missionRecords) {
List<MissionRecordFindResponse> missionRecords,
UrgingStatus urgingStatus) {
return new MissionRecordCalendarResponse(
missionStartedAt, missionFinishedAt, missionRecords);
missionStartedAt, missionFinishedAt, missionRecords, urgingStatus);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.depromeet.domain.missionRecord.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum UrgingStatus {
URGING,
NONE,
;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.depromeet.domain.notification.api;

import com.depromeet.domain.notification.application.PushService;
import com.depromeet.domain.notification.dto.request.PushUrgingSendRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@Tag(name = "7. [알림]", description = "알림 관련 API")
@RestController
@RequestMapping("/notifications")
@RequiredArgsConstructor
public class PushController {
private final PushService pushService;

@Operation(summary = "재촉하기", description = "당일 미션을 완료하지 않은 친구에게 재촉하기 Push Message를 발송합니다.")
@PostMapping("/urging")
public void urgingSend(@Valid @RequestBody PushUrgingSendRequest request) {
pushService.sendUrgingPush(request);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.depromeet.global.config.fcm;
package com.depromeet.domain.notification.application;

import com.google.api.core.ApiFuture;
import com.google.firebase.messaging.*;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.depromeet.domain.notification.application;

import static com.depromeet.global.common.constants.PushNotificationConstants.PUSH_URGING_TITLE;

import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.mission.dao.MissionRepository;
import com.depromeet.domain.mission.domain.Mission;
import com.depromeet.domain.notification.dao.NotificationRepository;
import com.depromeet.domain.notification.domain.Notification;
import com.depromeet.domain.notification.domain.NotificationType;
import com.depromeet.domain.notification.dto.request.PushUrgingSendRequest;
import com.depromeet.global.common.constants.PushNotificationConstants;
import com.depromeet.global.error.exception.CustomException;
import com.depromeet.global.error.exception.ErrorCode;
import com.depromeet.global.util.MemberUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class PushService {
private final MemberUtil memberUtil;
private final FcmService fcmService;
private final MissionRepository missionRepository;
private final NotificationRepository notificationRepository;

public void sendUrgingPush(PushUrgingSendRequest request) {
final Member currentMember = memberUtil.getCurrentMember();
final Mission mission =
missionRepository
.findById(request.missionId())
.orElseThrow(() -> new CustomException(ErrorCode.MISSION_NOT_FOUND));
final Member targetMember = mission.getMember();

validateSelfSending(currentMember.getId(), targetMember.getId());
validateMissionNotCompletedToday(mission);

fcmService.sendMessageSync(
targetMember.getFcmInfo().getFcmToken(),
PUSH_URGING_TITLE,
String.format(
PushNotificationConstants.PUSH_URGING_CONTENT,
currentMember.getProfile().getNickname(),
mission.getName()));
Notification notification =
Notification.createNotification(
NotificationType.MISSION_URGING, currentMember, targetMember);
notificationRepository.save(notification);
}

private void validateMissionNotCompletedToday(Mission mission) {
if (mission.isCompletedMissionToday()) {
throw new CustomException(ErrorCode.TODAY_COMPLETED_MISSION_SENDING_NOT_ALLOWED);
}
}

private void validateSelfSending(Long currentMemberId, Long targetMemberId) {
if (currentMemberId.equals(targetMemberId)) {
throw new CustomException(ErrorCode.SELF_SENDING_NOT_ALLOWED);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@AllArgsConstructor
public enum NotificationType {
FOLLOW("팔로우"),
MISSION_URGING("재촉하기"),
;

private final String value;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.depromeet.domain.notification.dto.request;

import jakarta.validation.constraints.NotNull;

public record PushUrgingSendRequest(
@NotNull(message = "missionId는 null일 수 없습니다.") Long missionId) {}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.depromeet.domain.common.constants;
package com.depromeet.global.common.constants;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand All @@ -9,4 +9,6 @@ public class PushNotificationConstants {
public static final String PUSH_SERVICE_CONTENT = "%s님이 회원님을 팔로우하기 시작했습니다🥳";
public static final String PUSH_NON_COMPLETE_MISSION_SERVICE_CONTENT =
"아직 오늘 미션을 완료하지 않았어요! 10분 동안 빠르게 완료해볼까요?";
public static final String PUSH_URGING_TITLE = "누가 내 미션을 기다린대요";
public static final String PUSH_URGING_CONTENT = "%s님이 %s 미션을 기다리고 있어요 🥺";
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public enum ErrorCode {
IMAGE_KEY_NOT_FOUND(HttpStatus.NOT_FOUND, "이미지 키를 찾을 수 없습니다."),
IMAGE_FILE_EXTENSION_NOT_FOUND(HttpStatus.NOT_FOUND, "이미지 파일 형식을 찾을 수 없습니다."),

// Notification
SELF_SENDING_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "본인에게 메세지를 전송할 수 없습니다."),
TODAY_COMPLETED_MISSION_SENDING_NOT_ALLOWED(
HttpStatus.BAD_REQUEST, "오늘 미션을 완료한 미션에는 메세지를 전송할 수 없습니다."),

// Reaction
REACTION_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 리액션을 찾을 수 없습니다."),
REACTION_ALREADY_EXISTS(HttpStatus.CONFLICT, "리액션은 미션기록 당 한번만 가능합니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.depromeet.global.config.fcm;
package com.depromeet.infra.config.fcm;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
Expand Down
Loading

0 comments on commit 67cf0b4

Please sign in to comment.