Skip to content

Commit

Permalink
Merge pull request #185 from depromeet/develop
Browse files Browse the repository at this point in the history
v0.0.5
  • Loading branch information
char-yb authored Aug 21, 2024
2 parents 210718b + 6c7bb05 commit c37ddc8
Show file tree
Hide file tree
Showing 45 changed files with 547 additions and 222 deletions.
Empty file added nullable_true
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import com.depromeet.stonebed.domain.auth.dto.response.AuthTokenResponse;
import com.depromeet.stonebed.domain.auth.dto.response.SocialClientResponse;
import com.depromeet.stonebed.domain.auth.dto.response.TokenPairResponse;
import com.depromeet.stonebed.domain.fcm.dao.FcmNotificationRepository;
import com.depromeet.stonebed.domain.member.dao.MemberRepository;
import com.depromeet.stonebed.domain.member.domain.Member;
import com.depromeet.stonebed.domain.member.domain.MemberRole;
import com.depromeet.stonebed.domain.member.domain.MemberStatus;
import com.depromeet.stonebed.domain.member.domain.Profile;
import com.depromeet.stonebed.domain.member.dto.request.CreateMemberRequest;
import com.depromeet.stonebed.domain.member.dto.request.NicknameCheckRequest;
import com.depromeet.stonebed.domain.missionRecord.dao.MissionRecordBoostRepository;
import com.depromeet.stonebed.domain.missionRecord.dao.MissionRecordRepository;
import com.depromeet.stonebed.global.error.ErrorCode;
import com.depromeet.stonebed.global.error.exception.CustomException;
import com.depromeet.stonebed.global.util.MemberUtil;
Expand All @@ -29,12 +31,15 @@
@RequiredArgsConstructor
@Transactional
public class AuthService {
private final FcmNotificationRepository fcmNotificationRepository;
private final MemberRepository memberRepository;
private final MissionRecordRepository missionRecordRepository;
private final MissionRecordBoostRepository missionRecordBoostRepository;

private final AppleClient appleClient;
private final KakaoClient kakaoClient;
private final JwtTokenService jwtTokenService;
private final MemberUtil memberUtil;
private final MemberRepository memberRepository;

public SocialClientResponse authenticateFromProvider(OAuthProvider provider, String token) {
/* token
Expand Down Expand Up @@ -86,9 +91,6 @@ public AuthTokenResponse registerMember(CreateMemberRequest request) {
Member currentMember = memberUtil.getCurrentMember();
// 사용자 회원가입
if (memberUtil.getMemberRole().equals(MemberRole.TEMPORARY.getValue())) {
// 닉네임 검증
memberUtil.checkNickname(NicknameCheckRequest.of(request.nickname()));

// 명시적 변경 감지
Member registerMember = registerMember(currentMember, request);

Expand Down Expand Up @@ -129,9 +131,12 @@ public void withdraw() {
*/
validateMemberStatusDelete(member.getStatus());
member.updateMemberRole(MemberRole.TEMPORARY);
member.updateProfile(Profile.createProfile("", ""));
memberRepository.flush();
withdrawMemberRelationByMemberId(member.getId());

jwtTokenService.deleteRefreshToken(member.getId());

memberRepository.deleteById(member.getId());
}

Expand All @@ -156,4 +161,10 @@ private void updateMemberStatus(Member member) {
member.updateStatus(MemberStatus.NORMAL);
}
}

private void withdrawMemberRelationByMemberId(Long memberId) {
missionRecordRepository.deleteAllByMember(memberId);
missionRecordBoostRepository.deleteAllByMember(memberId);
fcmNotificationRepository.deleteAllByMember(memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.depromeet.stonebed.domain.common;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import java.time.LocalDateTime;
import lombok.Getter;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseFullTimeEntity extends BaseTimeEntity {
@Column(name = "deleted_at", updatable = false)
private LocalDateTime deletedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -118,7 +119,11 @@ private String getNextCursor(List<FcmNotification> notifications) {
}

FcmNotification lastNotification = notifications.get(notifications.size() - 1);
return lastNotification.getCreatedAt().format(DATE_FORMATTER);

boolean hasNext =
notificationRepository.existsByCreatedAtLessThan(lastNotification.getCreatedAt());

return hasNext ? lastNotification.getCreatedAt().format(DATE_FORMATTER) : null;
}

public void checkAndSendBoostNotification(MissionRecord missionRecord) {
Expand Down Expand Up @@ -176,4 +181,33 @@ public void markNotificationAsRead(Long notificationId) {
notification.markAsRead();
notificationRepository.save(notification);
}

private List<FcmNotification> buildNotificationList(
String title, String message, List<String> tokens) {
List<FcmNotification> notifications = new ArrayList<>();

for (String token : tokens) {
Member member =
fcmRepository
.findByToken(token)
.map(FcmToken::getMember)
.orElseThrow(
() -> new CustomException(ErrorCode.FAILED_TO_FIND_FCM_TOKEN));

FcmNotification newNotification =
FcmNotification.create(
FcmNotificationType.MISSION, title, message, member, null, false);
notifications.add(newNotification);
}

return notifications;
}

public void sendAndNotifications(String title, String message, List<String> tokens) {
Notification notification = FcmNotificationUtil.buildNotification(title, message);
fcmService.sendMulticastMessage(notification, tokens);

List<FcmNotification> notifications = buildNotificationList(title, message, tokens);
notificationRepository.saveAll(notifications);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package com.depromeet.stonebed.domain.fcm.application;

import com.depromeet.stonebed.domain.fcm.dao.FcmRepository;
import com.depromeet.stonebed.domain.fcm.domain.FcmNotificationType;
import com.depromeet.stonebed.domain.fcm.domain.FcmToken;
import com.depromeet.stonebed.domain.missionRecord.dao.MissionRecordRepository;
import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecordStatus;
import com.depromeet.stonebed.global.common.constants.FcmNotificationConstants;
import com.depromeet.stonebed.global.util.FcmNotificationUtil;
import com.google.firebase.messaging.Notification;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
Expand All @@ -20,7 +17,7 @@
@Service
@RequiredArgsConstructor
public class FcmScheduledService {
private final FcmService fcmService;
private final FcmTokenService fcmTokenService;
private final FcmNotificationService fcmNotificationService;
private final FcmRepository fcmRepository;
private final MissionRecordRepository missionRecordRepository;
Expand All @@ -38,39 +35,26 @@ public void removeInactiveTokens() {
@Scheduled(cron = "0 0 9 * * ?")
public void sendDailyNotification() {
FcmNotificationConstants notificationConstants = FcmNotificationConstants.MISSION_START;
Notification notification =
FcmNotificationUtil.buildNotification(
notificationConstants.getTitle(), notificationConstants.getMessage());
String title = notificationConstants.getTitle();
String message = notificationConstants.getMessage();

fcmService.sendMulticastMessageToAll(notification);
log.info("모든 사용자에게 정규 알림 전송 완료");
List<String> tokens = fcmTokenService.getAllTokens();
fcmNotificationService.sendAndNotifications(title, message, tokens);

fcmNotificationService.saveNotification(
FcmNotificationType.MISSION,
notificationConstants.getTitle(),
notificationConstants.getMessage(),
null,
false);
log.info("모든 사용자에게 정규 알림 전송 및 저장 완료");
}

// 매일 19시 0분에 실행
@Scheduled(cron = "0 0 19 * * ?")
public void sendReminderToIncompleteMissions() {
FcmNotificationConstants notificationConstants = FcmNotificationConstants.MISSION_REMINDER;
Notification notification =
FcmNotificationUtil.buildNotification(
notificationConstants.getTitle(), notificationConstants.getMessage());
String title = notificationConstants.getTitle();
String message = notificationConstants.getMessage();

List<String> tokens = getIncompleteMissionTokens();
fcmService.sendMulticastMessage(notification, tokens);
log.info("미완료 미션 사용자에게 리마인더 전송 완료. 총 토큰 수: {}", tokens.size());
fcmNotificationService.sendAndNotifications(title, message, tokens);

fcmNotificationService.saveNotification(
FcmNotificationType.MISSION,
notificationConstants.getTitle(),
notificationConstants.getMessage(),
null,
false);
log.info("미완료 미션 사용자에게 리마인더 전송 및 저장 완료. 총 토큰 수: {}", tokens.size());
}

private List<String> getIncompleteMissionTokens() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public void sendMulticastMessageToAll(Notification notification) {
MulticastMessage message = buildMulticastMessage(notification, batchTokens);
sendMessage(message, batchTokens);
}

log.info("전체 메세지를 일괄 전송했습니다. 총 메세지 수: {}", totalTokens);
}

Expand All @@ -40,6 +41,7 @@ public void sendMulticastMessage(Notification notification, List<String> tokens)
MulticastMessage message = buildMulticastMessage(notification, batchTokens);
sendMessage(message, batchTokens);
}

log.info("리마인드 메세지를 일괄 전송했습니다. 총 메세지 수: {}", totalTokens);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface FcmNotificationRepository extends JpaRepository<FcmNotification, Long> {
List<FcmNotification> findByMemberId(Long memberId, Pageable pageable);
Expand All @@ -15,4 +18,11 @@ List<FcmNotification> findByMemberIdAndCreatedAtLessThanEqual(
Long memberId, LocalDateTime cursorDate, Pageable pageable);

Optional<FcmNotification> findByIdAndMember(Long id, Member member);

boolean existsByCreatedAtLessThan(LocalDateTime createdAt);

@Modifying
@Query(
"UPDATE FcmNotification fn SET fn.deletedAt = CURRENT_TIMESTAMP WHERE fn.member.id = :memberId")
void deleteAllByMember(@Param("memberId") Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package com.depromeet.stonebed.domain.fcm.domain;

import com.depromeet.stonebed.domain.common.BaseTimeEntity;
import com.depromeet.stonebed.domain.common.BaseFullTimeEntity;
import com.depromeet.stonebed.domain.member.domain.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;

@Getter
@Entity
@Table(name = "fcm_notification")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FcmNotification extends BaseTimeEntity {

@SQLDelete(sql = "UPDATE fcm_notification SET deleted_at = NOW() WHERE id = ?")
@SQLRestriction("deleted_at IS NULL")
public class FcmNotification extends BaseFullTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -21,11 +21,11 @@ public class FcmToken extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@OneToOne
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@Column(unique = true, nullable = false)
@Column(unique = true, nullable = true)
private String token;

public FcmToken(Member member, String token) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ public class FeedController {
@Operation(summary = "피드 조회", description = "내 피드를 조회하는 API입니다.")
@GetMapping
public FeedGetResponse getFeed(
@RequestParam(required = false) String cursor, @RequestParam int limit) {
FeedGetRequest request = new FeedGetRequest(cursor, limit);
@RequestParam(required = false) String cursor,
@RequestParam(required = false) Long memberId,
@RequestParam int limit) {
FeedGetRequest request = new FeedGetRequest(cursor, memberId, limit);
return feedService.getFeed(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
import com.depromeet.stonebed.domain.feed.dto.request.FeedGetRequest;
import com.depromeet.stonebed.domain.feed.dto.response.FeedContentGetResponse;
import com.depromeet.stonebed.domain.feed.dto.response.FeedGetResponse;
import com.depromeet.stonebed.domain.member.domain.Member;
import com.depromeet.stonebed.global.error.ErrorCode;
import com.depromeet.stonebed.global.error.exception.CustomException;
import com.depromeet.stonebed.global.util.MemberUtil;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -19,42 +17,57 @@
@RequiredArgsConstructor
public class FeedService {
private final FeedRepository feedRepository;
private final MemberUtil memberUtil;

@Transactional(readOnly = true)
public FeedGetResponse getFeed(FeedGetRequest request) {
Member currentMember = memberUtil.getCurrentMember();

List<FindFeedDto> feeds =
getFeeds(request.cursor(), currentMember.getId(), request.limit());
List<FindFeedDto> feeds = getFeeds(request.cursor(), request.memberId(), request.limit());

List<FeedContentGetResponse> feedContentList =
feeds.stream().map(FeedContentGetResponse::from).toList();

String nextCursor = getNextCursor(feeds, request.limit());

if (nextFeedNotExists(feeds, request.memberId())) {
nextCursor = null;
}

return FeedGetResponse.from(feedContentList, nextCursor);
}

private boolean nextFeedNotExists(List<FindFeedDto> feeds, Long memberId) {
Long lastRecordId = getLastRecordId(feeds);
if (lastRecordId == null) {
return true;
}

return feedRepository.getNextFeedContent(lastRecordId, memberId) == null;
}

private String getNextCursor(List<FindFeedDto> records, int limit) {
if (records.size() < limit) {
return null;
}

FindFeedDto lastRecord = records.get(records.size() - 1);
Long lastId = lastRecord.missionRecord().getId();
return String.valueOf(lastId);
return String.valueOf(getLastRecordId(records));
}

private List<FindFeedDto> getFeeds(String cursor, Long memberId, int limit) {
if (cursor == null || cursor.isEmpty()) {
return feedRepository.getFeedContents(memberId, limit);
private Long getLastRecordId(List<FindFeedDto> records) {
if (records.isEmpty()) {
return null;
}

return records.get(records.size() - 1).missionRecord().getId();
}

private List<FindFeedDto> getFeeds(String cursor, Long memberId, int limit) {
return feedRepository.getFeedContentsUsingCursor(parseCursor(cursor), memberId, limit);
}

private Long parseCursor(String cursor) {
if (cursor == null) {
return null;
}

try {
return Long.parseLong(cursor);
} catch (NumberFormatException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
public interface FeedRepositoryCustom {
List<FindFeedDto> getFeedContentsUsingCursor(Long missionRecordId, Long memberId, int limit);

List<FindFeedDto> getFeedContents(Long memberId, int limit);
FindFeedDto getNextFeedContent(Long missionRecordId, Long memberId);
}
Loading

0 comments on commit c37ddc8

Please sign in to comment.