From f371e6f79e7604dd87b49521254531a4810550e1 Mon Sep 17 00:00:00 2001 From: jeomxon Date: Wed, 18 Oct 2023 21:04:43 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20(#774)=20Member=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20alarmCheckedAt=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/entity/Member.java | 8 +++- .../member/service/MemberServiceTest.java | 4 ++ .../test/fixtures/MemberFixtures.java | 47 ++++++++++--------- .../test/persister/MemberTestPersister.java | 8 ++++ 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/backend/src/main/java/com/votogether/domain/member/entity/Member.java b/backend/src/main/java/com/votogether/domain/member/entity/Member.java index ce854e871..ea5f6e03c 100644 --- a/backend/src/main/java/com/votogether/domain/member/entity/Member.java +++ b/backend/src/main/java/com/votogether/domain/member/entity/Member.java @@ -59,6 +59,9 @@ public class Member extends BaseEntity { @Column(nullable = false, length = 20) private Roles roles; + @Column(columnDefinition = "datetime(6)", nullable = false) + private LocalDateTime alarmCheckedAt; + @Builder private Member( final String nickname, @@ -66,7 +69,8 @@ private Member( final Integer birthYear, final SocialType socialType, final String socialId, - final Roles roles + final Roles roles, + final LocalDateTime alarmCheckedAt ) { this.nickname = new Nickname(nickname); this.gender = gender; @@ -74,6 +78,7 @@ private Member( this.socialType = socialType; this.socialId = socialId; this.roles = roles; + this.alarmCheckedAt = alarmCheckedAt; } public static Member from(final KakaoMemberResponse response) { @@ -82,6 +87,7 @@ public static Member from(final KakaoMemberResponse response) { .socialType(SocialType.KAKAO) .socialId(String.valueOf(response.id())) .roles(Roles.MEMBER) + .alarmCheckedAt(LocalDateTime.now()) .build(); } diff --git a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java index 267bd961f..462642b08 100644 --- a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java +++ b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java @@ -101,6 +101,7 @@ void changeNickname() { .socialId("abc123") .socialType(SocialType.KAKAO) .roles(Roles.MEMBER) + .alarmCheckedAt(LocalDateTime.now()) .build(); String newNickname = "jeomxon"; Member savedMember = memberRepository.save(member); @@ -162,6 +163,7 @@ void changeNicknameThrowsExceptionNotPassedChangingCycle() { .socialId("abc123") .socialType(SocialType.KAKAO) .roles(Roles.MEMBER) + .alarmCheckedAt(LocalDateTime.now()) .build(); Member savedMember = memberRepository.save(member); @@ -188,6 +190,7 @@ void updateDetailsSuccess() { .socialType(SocialType.KAKAO) .socialId("123123123") .roles(Roles.MEMBER) + .alarmCheckedAt(LocalDateTime.now()) .build(); Member member = memberRepository.save(unsavedMember); MemberDetailRequest request = new MemberDetailRequest(Gender.FEMALE, 2000); @@ -225,6 +228,7 @@ void updateDetailsSameBirthYear() { .socialType(SocialType.KAKAO) .socialId("123123123") .roles(Roles.MEMBER) + .alarmCheckedAt(LocalDateTime.now()) .build(); Member member = memberRepository.save(unsavedMember); diff --git a/backend/src/test/java/com/votogether/test/fixtures/MemberFixtures.java b/backend/src/test/java/com/votogether/test/fixtures/MemberFixtures.java index ed1ab2631..0d14b2e7f 100644 --- a/backend/src/test/java/com/votogether/test/fixtures/MemberFixtures.java +++ b/backend/src/test/java/com/votogether/test/fixtures/MemberFixtures.java @@ -4,29 +4,30 @@ import com.votogether.domain.member.entity.vo.Gender; import com.votogether.domain.member.entity.vo.Roles; import com.votogether.domain.member.entity.vo.SocialType; +import java.time.LocalDateTime; public enum MemberFixtures { - MALE_UNDER_10("user1", Gender.MALE, 2015, "user1", Roles.MEMBER), - FEMALE_UNDER_10("user2", Gender.FEMALE, 2015, "user2", Roles.MEMBER), - MALE_10("user3", Gender.MALE, 2005, "user3", Roles.MEMBER), - FEMALE_10("user4", Gender.FEMALE, 2005, "user4", Roles.MEMBER), - MALE_20("user7", Gender.MALE, 1995, "user7", Roles.MEMBER), - FEMALE_20("user8", Gender.FEMALE, 1995, "user8", Roles.MEMBER), - MALE_30("user9", Gender.MALE, 1985, "user9", Roles.MEMBER), - FEMALE_30("user10", Gender.FEMALE, 1985, "user10", Roles.MEMBER), - MALE_40("user11", Gender.MALE, 1975, "user11", Roles.MEMBER), - FEMALE_40("user12", Gender.FEMALE, 1975, "user12", Roles.MEMBER), - MALE_50("user13", Gender.MALE, 1965, "user13", Roles.MEMBER), - FEMALE_50("user14", Gender.FEMALE, 1965, "user14", Roles.MEMBER), - MALE_60("user15", Gender.MALE, 1955, "user15", Roles.MEMBER), - FEMALE_60("user16", Gender.FEMALE, 1955, "user16", Roles.MEMBER), - MALE_70("user17", Gender.MALE, 1945, "user17", Roles.MEMBER), - FEMALE_70("user18", Gender.FEMALE, 1945, "user18", Roles.MEMBER), - MALE_80("user19", Gender.MALE, 1935, "user19", Roles.MEMBER), - FEMALE_80("user20", Gender.FEMALE, 1935, "user20", Roles.MEMBER), - MALE_OVER_90("user21", Gender.MALE, 1925, "user21", Roles.MEMBER), - FEMALE_OVER_90("user22", Gender.FEMALE, 1925, "user22", Roles.MEMBER), + MALE_UNDER_10("user1", Gender.MALE, 2015, "user1", Roles.MEMBER, LocalDateTime.now()), + FEMALE_UNDER_10("user2", Gender.FEMALE, 2015, "user2", Roles.MEMBER, LocalDateTime.now()), + MALE_10("user3", Gender.MALE, 2005, "user3", Roles.MEMBER, LocalDateTime.now()), + FEMALE_10("user4", Gender.FEMALE, 2005, "user4", Roles.MEMBER, LocalDateTime.now()), + MALE_20("user7", Gender.MALE, 1995, "user7", Roles.MEMBER, LocalDateTime.now()), + FEMALE_20("user8", Gender.FEMALE, 1995, "user8", Roles.MEMBER, LocalDateTime.now()), + MALE_30("user9", Gender.MALE, 1985, "user9", Roles.MEMBER, LocalDateTime.now()), + FEMALE_30("user10", Gender.FEMALE, 1985, "user10", Roles.MEMBER, LocalDateTime.now()), + MALE_40("user11", Gender.MALE, 1975, "user11", Roles.MEMBER, LocalDateTime.now()), + FEMALE_40("user12", Gender.FEMALE, 1975, "user12", Roles.MEMBER, LocalDateTime.now()), + MALE_50("user13", Gender.MALE, 1965, "user13", Roles.MEMBER, LocalDateTime.now()), + FEMALE_50("user14", Gender.FEMALE, 1965, "user14", Roles.MEMBER, LocalDateTime.now()), + MALE_60("user15", Gender.MALE, 1955, "user15", Roles.MEMBER, LocalDateTime.now()), + FEMALE_60("user16", Gender.FEMALE, 1955, "user16", Roles.MEMBER, LocalDateTime.now()), + MALE_70("user17", Gender.MALE, 1945, "user17", Roles.MEMBER, LocalDateTime.now()), + FEMALE_70("user18", Gender.FEMALE, 1945, "user18", Roles.MEMBER, LocalDateTime.now()), + MALE_80("user19", Gender.MALE, 1935, "user19", Roles.MEMBER, LocalDateTime.now()), + FEMALE_80("user20", Gender.FEMALE, 1935, "user20", Roles.MEMBER, LocalDateTime.now()), + MALE_OVER_90("user21", Gender.MALE, 1925, "user21", Roles.MEMBER, LocalDateTime.now()), + FEMALE_OVER_90("user22", Gender.FEMALE, 1925, "user22", Roles.MEMBER, LocalDateTime.now()), ; private final String nickname; @@ -34,19 +35,22 @@ public enum MemberFixtures { private final Integer birthYear; private final String socialId; private final Roles roles; + private final LocalDateTime alarmCheckedAt; MemberFixtures( final String nickname, final Gender gender, final Integer birthYear, final String socialId, - final Roles roles + final Roles roles, + final LocalDateTime alarmCheckedAt ) { this.nickname = nickname; this.gender = gender; this.birthYear = birthYear; this.socialId = socialId; this.roles = roles; + this.alarmCheckedAt = alarmCheckedAt; } public Member get() { @@ -57,6 +61,7 @@ public Member get() { .socialType(SocialType.KAKAO) .socialId(socialId) .roles(roles) + .alarmCheckedAt(alarmCheckedAt) .build(); } diff --git a/backend/src/test/java/com/votogether/test/persister/MemberTestPersister.java b/backend/src/test/java/com/votogether/test/persister/MemberTestPersister.java index e62e0a0b0..ec549908e 100644 --- a/backend/src/test/java/com/votogether/test/persister/MemberTestPersister.java +++ b/backend/src/test/java/com/votogether/test/persister/MemberTestPersister.java @@ -5,6 +5,7 @@ import com.votogether.domain.member.entity.vo.Roles; import com.votogether.domain.member.entity.vo.SocialType; import com.votogether.domain.member.repository.MemberRepository; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.RandomStringUtils; @@ -26,6 +27,7 @@ public final class MemberBuilder { private SocialType socialType; private String socialId; private Roles roles; + private LocalDateTime alarmCheckedAt; public MemberBuilder nickname(String nickname) { this.nickname = nickname; @@ -57,6 +59,11 @@ public MemberBuilder roles(Roles roles) { return this; } + public MemberBuilder alarmCheckedAt(LocalDateTime alarmCheckedAt) { + this.alarmCheckedAt = alarmCheckedAt; + return this; + } + public Member save() { Member member = Member.builder() .nickname(nickname == null ? RandomStringUtils.random(10, true, true) : nickname) @@ -65,6 +72,7 @@ public Member save() { .socialType(socialType == null ? SocialType.KAKAO : socialType) .socialId(socialId == null ? RandomStringUtils.random(10, true, true) : socialId) .roles(roles == null ? Roles.MEMBER : roles) + .alarmCheckedAt(alarmCheckedAt == null ? LocalDateTime.now() : alarmCheckedAt) .build(); return memberRepository.save(member); } From 216401f973b95acbc6294c4dc81d4e660da3a6c6 Mon Sep 17 00:00:00 2001 From: jeomxon Date: Wed, 18 Oct 2023 21:46:15 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20(#774)=20=EB=82=B4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=ED=95=A0=20=EB=95=8C?= =?UTF-8?q?=20=EC=B5=9C=EC=8B=A0=20=EC=95=8C=EB=A6=BC=EC=9D=B4=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=ED=8C=90?= =?UTF-8?q?=EB=8B=A8=ED=95=98=EC=97=AC=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votogether/domain/alarm/entity/Alarm.java | 8 +++++++ .../alarm/exception/AlarmExceptionType.java | 2 +- .../alarm/repository/AlarmRepository.java | 3 +++ .../ReportActionAlarmRepository.java | 2 ++ .../dto/response/MemberInfoResponse.java | 11 ++++++---- .../domain/member/entity/Member.java | 4 ++++ .../domain/member/service/MemberService.java | 21 +++++++++++++++++-- .../controller/MemberControllerTest.java | 5 +++-- 8 files changed, 47 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/votogether/domain/alarm/entity/Alarm.java b/backend/src/main/java/com/votogether/domain/alarm/entity/Alarm.java index cb833a54e..eb41b1550 100644 --- a/backend/src/main/java/com/votogether/domain/alarm/entity/Alarm.java +++ b/backend/src/main/java/com/votogether/domain/alarm/entity/Alarm.java @@ -15,6 +15,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import java.time.LocalDateTime; import java.util.Objects; import lombok.AccessLevel; import lombok.Builder; @@ -74,4 +75,11 @@ public void checkOwner(final Member member) { } } + public LocalDateTime getLatestAlarmCreatedAt(final LocalDateTime reportActionAlarmCreatedAt) { + if (this.getCreatedAt().isAfter(reportActionAlarmCreatedAt)) { + return this.getCreatedAt(); + } + return reportActionAlarmCreatedAt; + } + } diff --git a/backend/src/main/java/com/votogether/domain/alarm/exception/AlarmExceptionType.java b/backend/src/main/java/com/votogether/domain/alarm/exception/AlarmExceptionType.java index e60ab801d..e28b62f49 100644 --- a/backend/src/main/java/com/votogether/domain/alarm/exception/AlarmExceptionType.java +++ b/backend/src/main/java/com/votogether/domain/alarm/exception/AlarmExceptionType.java @@ -6,7 +6,7 @@ @Getter public enum AlarmExceptionType implements ExceptionType { - NOT_FOUND_ACTION(1300, "신고조치알림이 존재하지 않습니다."), + NOT_FOUND_ACTION(1300, "신고 조치 알림이 존재하지 않습니다."), NOT_FOUND(1301, "알림이 존재하지 않습니다."), NOT_OWNER(1302, "알림을 읽을 대상이 아닙니다."), NOT_FOUND_ACTION_TYPE(1303, "등록되지 않은 알림 동작입니다."), diff --git a/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java b/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java index 3050ce13f..4968a4dde 100644 --- a/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java +++ b/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java @@ -3,6 +3,7 @@ import com.votogether.domain.alarm.entity.Alarm; import com.votogether.domain.member.entity.Member; import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; @@ -13,4 +14,6 @@ public interface AlarmRepository extends JpaRepository { List findAllByMember(final Member member); + Optional findByMemberOrderByCreatedAtDesc(final Member member); + } diff --git a/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java b/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java index abe814924..9a28827c8 100644 --- a/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java +++ b/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java @@ -13,4 +13,6 @@ public interface ReportActionAlarmRepository extends JpaRepository findByIdAndMember(final Long Id, final Member member); + Optional findByMemberOrderByCreatedAtDesc(final Member member); + } diff --git a/backend/src/main/java/com/votogether/domain/member/dto/response/MemberInfoResponse.java b/backend/src/main/java/com/votogether/domain/member/dto/response/MemberInfoResponse.java index cfa073055..5ff5fe81b 100644 --- a/backend/src/main/java/com/votogether/domain/member/dto/response/MemberInfoResponse.java +++ b/backend/src/main/java/com/votogether/domain/member/dto/response/MemberInfoResponse.java @@ -15,13 +15,16 @@ public record MemberInfoResponse( @Schema(description = "출생년도", example = "2002") Integer birthYear, - @Schema(description = "권한", example = "MEMBER") - Roles roles, - @Schema(description = "작성한 게시글 수", example = "5") long postCount, @Schema(description = "투표한 수", example = "10") - long voteCount + long voteCount, + + @Schema(description = "권한", example = "MEMBER") + Roles roles, + + @Schema(description = "최신 알림 존재 여부", example = "false") + boolean hasLatestAlarm ) { } diff --git a/backend/src/main/java/com/votogether/domain/member/entity/Member.java b/backend/src/main/java/com/votogether/domain/member/entity/Member.java index ea5f6e03c..17801f4b6 100644 --- a/backend/src/main/java/com/votogether/domain/member/entity/Member.java +++ b/backend/src/main/java/com/votogether/domain/member/entity/Member.java @@ -123,6 +123,10 @@ public boolean hasEssentialInfo() { return (this.gender != null && this.birthYear != null); } + public boolean hasLatestAlarmCompareTo(final LocalDateTime latestAlarmCreatedAt) { + return alarmCheckedAt.isBefore(latestAlarmCreatedAt); + } + public String getNickname() { return this.nickname.getValue(); } diff --git a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java index 81d6dad7d..801adb883 100644 --- a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java +++ b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java @@ -1,7 +1,10 @@ package com.votogether.domain.member.service; import com.votogether.domain.alarm.entity.Alarm; +import com.votogether.domain.alarm.entity.ReportActionAlarm; +import com.votogether.domain.alarm.exception.AlarmExceptionType; import com.votogether.domain.alarm.repository.AlarmRepository; +import com.votogether.domain.alarm.repository.ReportActionAlarmRepository; import com.votogether.domain.member.dto.request.MemberDetailRequest; import com.votogether.domain.member.dto.response.MemberInfoResponse; import com.votogether.domain.member.entity.Member; @@ -23,6 +26,7 @@ import com.votogether.domain.vote.repository.VoteRepository; import com.votogether.global.exception.BadRequestException; import com.votogether.global.exception.NotFoundException; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -43,6 +47,7 @@ public class MemberService { private final ReportRepository reportRepository; private final CommentRepository commentRepository; private final AlarmRepository alarmRepository; + private final ReportActionAlarmRepository reportActionAlarmRepository; @Transactional public Member register(final Member member) { @@ -73,17 +78,29 @@ public Member findById(final Long memberId) { public MemberInfoResponse findMemberInfo(final Member member) { final MemberMetric memberMetric = memberMetricRepository.findByMember(member) .orElseThrow(() -> new NotFoundException(MemberExceptionType.NOT_FOUND_METRIC)); + final boolean hasLatestAlarm = hasLatestAlarm(member); return new MemberInfoResponse( member.getNickname(), member.getGender(), member.getBirthYear(), - member.getRoles(), memberMetric.getPostCount(), - memberMetric.getVoteCount() + memberMetric.getVoteCount(), + member.getRoles(), + hasLatestAlarm ); } + private boolean hasLatestAlarm(final Member member) { + final Alarm alarm = alarmRepository.findByMemberOrderByCreatedAtDesc(member) + .orElseThrow(() -> new NotFoundException(AlarmExceptionType.NOT_FOUND)); + final ReportActionAlarm reportActionAlarm = reportActionAlarmRepository.findByMemberOrderByCreatedAtDesc(member) + .orElseThrow(() -> new NotFoundException(AlarmExceptionType.NOT_FOUND_ACTION)); + + final LocalDateTime latestAlarmCreatedAt = alarm.getLatestAlarmCreatedAt(reportActionAlarm.getCreatedAt()); + return member.hasLatestAlarmCompareTo(latestAlarmCreatedAt); + } + @Transactional public void changeNickname(final Member member, final String nickname) { validateExistentNickname(nickname); diff --git a/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java b/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java index d0758af95..be6d0db53 100644 --- a/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java +++ b/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java @@ -55,9 +55,10 @@ void findMemberInfo() throws Exception { "저문", Gender.MALE, 1988, - Roles.MEMBER, 0, - 0 + 0, + Roles.MEMBER, + false ); given(tokenProcessor.resolveToken(anyString())).willReturn("token"); From 1dc8ec2d5be166b867d5fb2ee5c1d145cd86e7ec Mon Sep 17 00:00:00 2001 From: jeomxon Date: Wed, 18 Oct 2023 21:55:15 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20(#774)=20=EC=B5=9C=EC=8B=A0=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberController.java | 6 ++++++ .../java/com/votogether/domain/member/entity/Member.java | 4 ++++ .../com/votogether/domain/member/service/MemberService.java | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/backend/src/main/java/com/votogether/domain/member/controller/MemberController.java b/backend/src/main/java/com/votogether/domain/member/controller/MemberController.java index 86c84b39f..3cd32d3d6 100644 --- a/backend/src/main/java/com/votogether/domain/member/controller/MemberController.java +++ b/backend/src/main/java/com/votogether/domain/member/controller/MemberController.java @@ -46,6 +46,12 @@ public ResponseEntity updateDetails( return ResponseEntity.ok().build(); } + @PatchMapping("/me/check-alarm") + public ResponseEntity checkLatestAlarm(@Auth final Member member) { + memberService.checkLatestAlarm(member); + return ResponseEntity.ok().build(); + } + @DeleteMapping("/me/delete") public ResponseEntity deleteMember(@Auth final Member member) { memberService.deleteMember(member); diff --git a/backend/src/main/java/com/votogether/domain/member/entity/Member.java b/backend/src/main/java/com/votogether/domain/member/entity/Member.java index 17801f4b6..8190739ad 100644 --- a/backend/src/main/java/com/votogether/domain/member/entity/Member.java +++ b/backend/src/main/java/com/votogether/domain/member/entity/Member.java @@ -127,6 +127,10 @@ public boolean hasLatestAlarmCompareTo(final LocalDateTime latestAlarmCreatedAt) return alarmCheckedAt.isBefore(latestAlarmCreatedAt); } + public void checkAlarm() { + alarmCheckedAt = LocalDateTime.now(); + } + public String getNickname() { return this.nickname.getValue(); } diff --git a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java index 801adb883..2e0619166 100644 --- a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java +++ b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java @@ -129,6 +129,11 @@ private void validateExistentDetails(final Member member) { } } + @Transactional + public void checkLatestAlarm(final Member member) { + member.checkAlarm(); + } + @Transactional public void deleteMember(final Member member) { final List posts = deletePosts(member); From 5ebd67502f961e215d2e880fbd9800d3a4f54608 Mon Sep 17 00:00:00 2001 From: jeomxon Date: Wed, 18 Oct 2023 22:23:43 +0900 Subject: [PATCH 04/12] =?UTF-8?q?refactor:=20(#774)=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EB=82=B4=EC=97=AD=20=EB=B0=8F=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=20=EC=A1=B0=EC=B9=98=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=EC=9D=B4=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberControllerDocs.java | 15 +++++++++- .../domain/member/service/MemberService.java | 28 +++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java b/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java index 082b424d0..7a0d600f5 100644 --- a/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java +++ b/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java @@ -5,6 +5,7 @@ import com.votogether.domain.member.dto.response.MemberInfoResponse; import com.votogether.domain.member.entity.Member; import com.votogether.global.exception.ExceptionResponse; +import com.votogether.global.jwt.Auth; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -17,7 +18,15 @@ public interface MemberControllerDocs { @Operation(summary = "회원 정보 조회", description = "회원 정보를 조회한다.") - @ApiResponse(responseCode = "200", description = "회원 정보 조회 성공") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "회원 정보 조회 성공"), + @ApiResponse( + responseCode = "400", + description = "회원에 해당하는 통계 정보가 없는 경우", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class) + ) + ) + }) ResponseEntity findMemberInfo(final Member member); @Operation(summary = "회원 닉네임 변경", description = "회원 닉네임을 변경한다.") @@ -48,6 +57,10 @@ ResponseEntity updateDetails( final Member member ); + @Operation(summary = "회원 최신 알림 확인", description = "회원의 최신 알림 읽은 시간을 수정한다.") + @ApiResponse(responseCode = "200", description = "최신 알림 읽기 성공") + ResponseEntity checkLatestAlarm(@Auth final Member member); + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴한다.") @ApiResponse(responseCode = "200", description = "회원 탈퇴 성공") ResponseEntity deleteMember(final Member member); diff --git a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java index 2e0619166..9ab1a26c5 100644 --- a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java +++ b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java @@ -2,7 +2,6 @@ import com.votogether.domain.alarm.entity.Alarm; import com.votogether.domain.alarm.entity.ReportActionAlarm; -import com.votogether.domain.alarm.exception.AlarmExceptionType; import com.votogether.domain.alarm.repository.AlarmRepository; import com.votogether.domain.alarm.repository.ReportActionAlarmRepository; import com.votogether.domain.member.dto.request.MemberDetailRequest; @@ -92,15 +91,32 @@ public MemberInfoResponse findMemberInfo(final Member member) { } private boolean hasLatestAlarm(final Member member) { - final Alarm alarm = alarmRepository.findByMemberOrderByCreatedAtDesc(member) - .orElseThrow(() -> new NotFoundException(AlarmExceptionType.NOT_FOUND)); - final ReportActionAlarm reportActionAlarm = reportActionAlarmRepository.findByMemberOrderByCreatedAtDesc(member) - .orElseThrow(() -> new NotFoundException(AlarmExceptionType.NOT_FOUND_ACTION)); + final Optional maybeAlarm = alarmRepository.findByMemberOrderByCreatedAtDesc(member); + final Optional maybeReportActionAlarm = + reportActionAlarmRepository.findByMemberOrderByCreatedAtDesc(member); - final LocalDateTime latestAlarmCreatedAt = alarm.getLatestAlarmCreatedAt(reportActionAlarm.getCreatedAt()); + if (maybeAlarm.isEmpty() && maybeReportActionAlarm.isEmpty()) { + return false; + } + final LocalDateTime latestAlarmCreatedAt = getLatestAlarmCreatedAt(maybeAlarm, maybeReportActionAlarm); return member.hasLatestAlarmCompareTo(latestAlarmCreatedAt); } + private LocalDateTime getLatestAlarmCreatedAt( + final Optional maybeAlarm, + final Optional maybeReportActionAlarm + ) { + if (maybeAlarm.isPresent()) { + return maybeAlarm.get().getCreatedAt(); + } + if (maybeReportActionAlarm.isPresent()) { + return maybeReportActionAlarm.get().getCreatedAt(); + } + final Alarm alarm = maybeAlarm.get(); + final ReportActionAlarm reportActionAlarm = maybeReportActionAlarm.get(); + return alarm.getLatestAlarmCreatedAt(reportActionAlarm.getCreatedAt()); + } + @Transactional public void changeNickname(final Member member, final String nickname) { validateExistentNickname(nickname); From 4cb655b98c0d39a0ee94d357c6b9dac1d723c2b7 Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 02:13:23 +0900 Subject: [PATCH 05/12] =?UTF-8?q?test:=20(#774)=20Alarm,=20ReportActionAla?= =?UTF-8?q?rm=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20persister=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/persister/AlarmTestPersister.java | 66 +++++++++++++++++++ .../ReportActionAlarmTestPersister.java | 66 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 backend/src/test/java/com/votogether/test/persister/AlarmTestPersister.java create mode 100644 backend/src/test/java/com/votogether/test/persister/ReportActionAlarmTestPersister.java diff --git a/backend/src/test/java/com/votogether/test/persister/AlarmTestPersister.java b/backend/src/test/java/com/votogether/test/persister/AlarmTestPersister.java new file mode 100644 index 000000000..08a7b6a4a --- /dev/null +++ b/backend/src/test/java/com/votogether/test/persister/AlarmTestPersister.java @@ -0,0 +1,66 @@ +package com.votogether.test.persister; + +import com.votogether.domain.alarm.entity.Alarm; +import com.votogether.domain.alarm.entity.vo.AlarmType; +import com.votogether.domain.alarm.repository.AlarmRepository; +import com.votogether.domain.member.entity.Member; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Persister +public class AlarmTestPersister { + + private final AlarmRepository alarmRepository; + private final MemberTestPersister memberTestPersister; + + public AlarmBuilder builder() { + return new AlarmBuilder(); + } + + public final class AlarmBuilder { + + private Member member; + private AlarmType alarmType; + private Long targetId; + private String detail; + private boolean isChecked; + + public AlarmBuilder member(Member member) { + this.member = member; + return this; + } + + public AlarmBuilder alarmType(AlarmType alarmType) { + this.alarmType = alarmType; + return this; + } + + public AlarmBuilder targetId(Long targetId) { + this.targetId = targetId; + return this; + } + + public AlarmBuilder detail(String detail) { + this.detail = detail; + return this; + } + + public AlarmBuilder isChecked(boolean isChecked) { + this.isChecked = isChecked; + return this; + } + + public Alarm save() { + Alarm alarm = Alarm.builder() + .member(member == null ? memberTestPersister.builder().save() : member) + .alarmType(alarmType == null ? AlarmType.COMMENT : alarmType) + .targetId(targetId == null ? 1L : targetId) + .detail(detail == null ? "detail" : detail) + .isChecked(false) + .build(); + return alarmRepository.save(alarm); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/test/persister/ReportActionAlarmTestPersister.java b/backend/src/test/java/com/votogether/test/persister/ReportActionAlarmTestPersister.java new file mode 100644 index 000000000..62d56421e --- /dev/null +++ b/backend/src/test/java/com/votogether/test/persister/ReportActionAlarmTestPersister.java @@ -0,0 +1,66 @@ +package com.votogether.test.persister; + +import com.votogether.domain.alarm.entity.ReportActionAlarm; +import com.votogether.domain.alarm.repository.ReportActionAlarmRepository; +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.report.entity.vo.ReportType; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Persister +public class ReportActionAlarmTestPersister { + + private final ReportActionAlarmRepository reportActionAlarmRepository; + private final MemberTestPersister memberTestPersister; + + public ReportActionAlarmBuilder builder() { + return new ReportActionAlarmBuilder(); + } + + public final class ReportActionAlarmBuilder { + + private Member member; + private ReportType reportType; + private String target; + private String reasons; + private boolean isChecked; + + public ReportActionAlarmBuilder member(Member member) { + this.member = member; + return this; + } + + public ReportActionAlarmBuilder reportType(ReportType reportType) { + this.reportType = reportType; + return this; + } + + public ReportActionAlarmBuilder target(String target) { + this.target = target; + return this; + } + + public ReportActionAlarmBuilder reasons(String reasons) { + this.reasons = reasons; + return this; + } + + public ReportActionAlarmBuilder isChecked(boolean isChecked) { + this.isChecked = isChecked; + return this; + } + + public ReportActionAlarm save() { + ReportActionAlarm reportActionAlarm = ReportActionAlarm.builder() + .member(member == null ? memberTestPersister.builder().save() : member) + .reportType(reportType == null ? ReportType.NICKNAME : reportType) + .target(target == null ? "target" : target) + .reasons(reasons == null ? "reasons" : reasons) + .isChecked(false) + .build(); + return reportActionAlarmRepository.save(reportActionAlarm); + } + + } + +} From f75e1027f2141716675043a596e6e20947830dbe Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 02:21:19 +0900 Subject: [PATCH 06/12] =?UTF-8?q?test:=20(#774)=20=EC=B5=9C=EC=8B=A0=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EB=82=B4=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EC=8B=9C=20=EC=B5=9C=EC=8B=A0=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/alarm/entity/AlarmTest.java | 23 ++++ .../controller/MemberControllerTest.java | 20 +++ .../domain/member/entity/MemberTest.java | 49 +++++++ .../member/service/MemberServiceTest.java | 130 ++++++++++++++++++ 4 files changed, 222 insertions(+) diff --git a/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java b/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java index a0cc671f2..69f8f6483 100644 --- a/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java +++ b/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java @@ -7,6 +7,7 @@ import com.votogether.domain.member.entity.Member; import com.votogether.global.exception.BadRequestException; import com.votogether.test.fixtures.MemberFixtures; +import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; @@ -54,4 +55,26 @@ void checkOwner() { .hasMessage("알림을 읽을 대상이 아닙니다."); } + @Test + @DisplayName("알림이 생성된 시각과 인자로 받은 시각을 비교하여 최신 시각을 반환한다.") + void getLatestAlarmCreatedAt() { + // given + Member member = MemberFixtures.MALE_30.get(); + Alarm alarm = Alarm.builder() + .member(member) + .alarmType(AlarmType.COMMENT) + .targetId(1L) + .detail("detail") + .isChecked(false) + .build(); + ReflectionTestUtils.setField(alarm, "createdAt", LocalDateTime.of(2023, 10, 18, 12, 0)); + LocalDateTime now = LocalDateTime.now(); + + // when + LocalDateTime latestAlarmCreatedAt = alarm.getLatestAlarmCreatedAt(now); + + // then + assertThat(latestAlarmCreatedAt).isEqualTo(now); + } + } diff --git a/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java b/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java index be6d0db53..1372b8a18 100644 --- a/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java +++ b/backend/src/test/java/com/votogether/domain/member/controller/MemberControllerTest.java @@ -220,6 +220,26 @@ void invalidNullOfBirthYear(Integer birthYear) throws Exception { } + @Test + @DisplayName("최신 알림 읽기에 성공하면 200을 반환한다.") + void checkLatestAlarm() throws Exception { + // given + TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L); + given(tokenProcessor.resolveToken(anyString())).willReturn("token"); + given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload); + given(memberService.findById(anyLong())).willReturn(MemberFixtures.FEMALE_20.get()); + + willDoNothing().given(memberService).checkLatestAlarm(any(Member.class)); + + // when, then + RestAssuredMockMvc + .given().log().all() + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .when().patch("/members/me/check-alarm") + .then().log().all() + .statusCode(HttpStatus.OK.value()); + } + @Test @DisplayName("회원 탈퇴에 성공하면 204를 반환한다.") void deleteMember() throws Exception { diff --git a/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java index b3eb1f33a..b9a053f63 100644 --- a/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java +++ b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java @@ -6,6 +6,7 @@ import com.votogether.domain.member.entity.vo.Gender; import com.votogether.domain.member.entity.vo.SocialType; import com.votogether.global.exception.BadRequestException; +import com.votogether.test.fixtures.MemberFixtures; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -112,4 +113,52 @@ void notAllowedChangeToInitialNicknamePrefix() { } + @Test + @DisplayName("회원은 알림을 확인하면 이전에 저장되어있던 시간과는 다르다.") + void checkAlarm() { + // given + Member member = MemberFixtures.MALE_20.get(); + LocalDateTime beforeAlarmCheckedAt = member.getAlarmCheckedAt(); + + // when + member.checkAlarm(); + + // then + LocalDateTime afterAlarmCheckedAt = member.getAlarmCheckedAt(); + assertThat(beforeAlarmCheckedAt).isNotEqualTo(afterAlarmCheckedAt); + } + + @Nested + @DisplayName("회원의 최신 알림 읽은 시각이 인자로 받은 시각보다") + class HasLatestAlarmCompareTo { + + @Test + @DisplayName("이전이면 true를 반환한다.") + void returnsTrue() { + // given + Member member = MemberFixtures.MALE_20.get(); + + // when + boolean hasLatestAlarm = member.hasLatestAlarmCompareTo(LocalDateTime.now()); + + // then + assertThat(hasLatestAlarm).isTrue(); + } + + @Test + @DisplayName("이후이면 false를 반환한다.") + void returnsFalse() { + // given + Member member = MemberFixtures.MALE_20.get(); + LocalDateTime beforeTime = LocalDateTime.of(2023, 10, 18, 12, 0); + + // when + boolean hasLatestAlarm = member.hasLatestAlarmCompareTo(beforeTime); + + // then + assertThat(hasLatestAlarm).isFalse(); + } + + } + } diff --git a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java index 462642b08..ed5be9df5 100644 --- a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java +++ b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java @@ -5,9 +5,12 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.junit.jupiter.api.Assertions.assertAll; +import com.votogether.domain.alarm.entity.Alarm; +import com.votogether.domain.alarm.entity.ReportActionAlarm; import com.votogether.domain.category.entity.Category; import com.votogether.domain.category.repository.CategoryRepository; import com.votogether.domain.member.dto.request.MemberDetailRequest; +import com.votogether.domain.member.dto.response.MemberInfoResponse; import com.votogether.domain.member.entity.Member; import com.votogether.domain.member.entity.MemberCategory; import com.votogether.domain.member.entity.vo.Gender; @@ -24,9 +27,12 @@ import com.votogether.domain.report.entity.vo.ReportType; import com.votogether.domain.report.repository.ReportRepository; import com.votogether.global.exception.BadRequestException; +import com.votogether.global.exception.NotFoundException; import com.votogether.test.ServiceTest; import com.votogether.test.fixtures.MemberFixtures; +import com.votogether.test.persister.AlarmTestPersister; import com.votogether.test.persister.PostTestPersister; +import com.votogether.test.persister.ReportActionAlarmTestPersister; import jakarta.persistence.EntityManager; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; @@ -37,6 +43,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; class MemberServiceTest extends ServiceTest { @@ -67,6 +74,12 @@ class MemberServiceTest extends ServiceTest { @Autowired PostTestPersister postTestPersister; + @Autowired + AlarmTestPersister alarmTestPersister; + + @Autowired + ReportActionAlarmTestPersister reportActionAlarmTestPersister; + @Autowired EntityManager em; @@ -242,6 +255,123 @@ void updateDetailsSameBirthYear() { } + @Nested + @DisplayName("내 정보 조회를 할 때") + class FindMemberInfo { + + @Test + @DisplayName("정상적으로 조회가 된다.") + void success() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertSoftly(softly -> { + softly.assertThat(memberInfoResponse.gender()).isEqualTo(Gender.MALE); + softly.assertThat(memberInfoResponse.birthYear()).isEqualTo(1995); + softly.assertThat(memberInfoResponse.postCount()).isZero(); + softly.assertThat(memberInfoResponse.voteCount()).isZero(); + softly.assertThat(memberInfoResponse.hasLatestAlarm()).isFalse(); + }); + } + + @Test + @DisplayName("메트릭 정보가 없는 경우 예외가 발생한다.") + void throwsExceptionWhenNoMetrics() { + // given + Member member = memberTestPersister.builder().save(); + + // when, then + assertThatThrownBy(() -> memberService.findMemberInfo(member)) + .isInstanceOf(NotFoundException.class) + .hasMessage("메트릭 정보가 존재하지 않습니다."); + } + + @Test + @DisplayName("게시글 내역 알림이 존재한다면 회원의 최신 알림 확인 시각과 비교하여 게시글 알림이 더 최신인 경우 true를 반환한다.") + void returnsTrueWhenPostAlarmIsLatest() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + alarmTestPersister.builder().member(member).save(); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertThat(memberInfoResponse.hasLatestAlarm()).isTrue(); + } + + @Test + @DisplayName("신고 조치 내역 알림이 존재한다면 회원의 최신 알림 확인 시각과 비교하여 게시글 알림이 더 최신인 경우 true를 반환한다.") + void returnsTrueWhenReportActionAlarmIsLatest() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + reportActionAlarmTestPersister.builder().member(member).save(); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertThat(memberInfoResponse.hasLatestAlarm()).isTrue(); + } + + @Test + @DisplayName("신고 조치 내역 알림과 게시글 내역 알림이 모두 존재한다면 회원의 최신 알림 확인 시각과 비교하여 신고 조치 알림이 더 최신인 경우 true를 반환한다.") + void returnsTrueWhenReportActionAlarmOrPostAlarmIsLatest() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + alarmTestPersister.builder().member(member).save(); + reportActionAlarmTestPersister.builder().member(member).save(); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertThat(memberInfoResponse.hasLatestAlarm()).isTrue(); + } + + @Test + @DisplayName("신고 조치 내역 알림이 존재하지만, 회원의 최신 알림 확인 시각이 더 최신인 경우 false를 반환한다.") + void returnsTrueWhenReportActionAlarmOrPostAlarmIsNotLatest() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + Alarm alarm = alarmTestPersister.builder().member(member).save(); + ReportActionAlarm reportActionAlarm = reportActionAlarmTestPersister.builder().member(member).save(); + + ReflectionTestUtils.setField(alarm, "createdAt", LocalDateTime.now().minusDays(1)); + ReflectionTestUtils.setField(reportActionAlarm, "createdAt", LocalDateTime.now().minusDays(1)); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertThat(memberInfoResponse.hasLatestAlarm()).isFalse(); + } + + } + + @Test + @DisplayName("최신 알림을 확인한다.") + void checkLatestAlarm() { + // given + Member member = memberTestPersister.builder().save(); + LocalDateTime beforeTime = member.getCreatedAt(); + + // when + memberService.checkLatestAlarm(member); + + // then + assertThat(member.getAlarmCheckedAt()).isNotEqualTo(beforeTime); + } + @Nested @DisplayName("회원 탈퇴를 할 때") class DeleteMember { From 982f08c44e8f82fa66d55a8999c8f679e63b79ba Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 10:11:25 +0900 Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20(#774)=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=20=EC=95=8C=EB=A6=BC=20=EB=B9=84=EA=B5=90=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votogether/domain/member/service/MemberService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java index 9ab1a26c5..89b97f53d 100644 --- a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java +++ b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java @@ -106,12 +106,12 @@ private LocalDateTime getLatestAlarmCreatedAt( final Optional maybeAlarm, final Optional maybeReportActionAlarm ) { - if (maybeAlarm.isPresent()) { - return maybeAlarm.get().getCreatedAt(); - } - if (maybeReportActionAlarm.isPresent()) { + if (maybeAlarm.isEmpty()) { return maybeReportActionAlarm.get().getCreatedAt(); } + if (maybeReportActionAlarm.isEmpty()) { + return maybeAlarm.get().getCreatedAt(); + } final Alarm alarm = maybeAlarm.get(); final ReportActionAlarm reportActionAlarm = maybeReportActionAlarm.get(); return alarm.getLatestAlarmCreatedAt(reportActionAlarm.getCreatedAt()); From 8421e31827de0f808bf1eacd0e5061af408843cf Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 10:30:40 +0900 Subject: [PATCH 08/12] =?UTF-8?q?test:=20(#774)=20=EC=B5=9C=EC=8B=A0=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8A=94=20=EA=B2=80=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/alarm/entity/AlarmTest.java | 2 +- .../domain/member/entity/MemberTest.java | 4 +- .../member/service/MemberServiceTest.java | 44 ++++++++++++++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java b/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java index 69f8f6483..6a6668d7c 100644 --- a/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java +++ b/backend/src/test/java/com/votogether/domain/alarm/entity/AlarmTest.java @@ -67,7 +67,7 @@ void getLatestAlarmCreatedAt() { .detail("detail") .isChecked(false) .build(); - ReflectionTestUtils.setField(alarm, "createdAt", LocalDateTime.of(2023, 10, 18, 12, 0)); + ReflectionTestUtils.setField(alarm, "createdAt", LocalDateTime.of(2010, 10, 18, 12, 0)); LocalDateTime now = LocalDateTime.now(); // when diff --git a/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java index b9a053f63..d571d29ca 100644 --- a/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java +++ b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java @@ -16,7 +16,7 @@ class MemberTest { @Nested - @DisplayName("닉네임을 주기에 따라 변경하는 경우") + @DisplayName("닉네임을 주기에 따라 변경하는 경우 ") class ChangeNicknameByCycle { @Test @@ -150,7 +150,7 @@ void returnsTrue() { void returnsFalse() { // given Member member = MemberFixtures.MALE_20.get(); - LocalDateTime beforeTime = LocalDateTime.of(2023, 10, 18, 12, 0); + LocalDateTime beforeTime = LocalDateTime.of(2010, 10, 18, 12, 0); // when boolean hasLatestAlarm = member.hasLatestAlarmCompareTo(beforeTime); diff --git a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java index ed5be9df5..7b6fc81f5 100644 --- a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java +++ b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java @@ -307,7 +307,7 @@ void returnsTrueWhenPostAlarmIsLatest() { } @Test - @DisplayName("신고 조치 내역 알림이 존재한다면 회원의 최신 알림 확인 시각과 비교하여 게시글 알림이 더 최신인 경우 true를 반환한다.") + @DisplayName("신고 조치 내역 알림이 존재한다면 회원의 최신 알림 확인 시각과 비교하여 신고 조치 내역 알림이 더 최신인 경우 true를 반환한다.") void returnsTrueWhenReportActionAlarmIsLatest() { // given Member member = memberTestPersister.builder().save(); @@ -322,7 +322,7 @@ void returnsTrueWhenReportActionAlarmIsLatest() { } @Test - @DisplayName("신고 조치 내역 알림과 게시글 내역 알림이 모두 존재한다면 회원의 최신 알림 확인 시각과 비교하여 신고 조치 알림이 더 최신인 경우 true를 반환한다.") + @DisplayName("신고 조치 내역 알림과 게시글 내역 알림이 모두 존재한다면 회원의 최신 알림 확인 시각과 비교하여 알림들이 더 최신인 경우 true를 반환한다.") void returnsTrueWhenReportActionAlarmOrPostAlarmIsLatest() { // given Member member = memberTestPersister.builder().save(); @@ -337,6 +337,46 @@ void returnsTrueWhenReportActionAlarmOrPostAlarmIsLatest() { assertThat(memberInfoResponse.hasLatestAlarm()).isTrue(); } + @Test + @DisplayName("게시글 내역 알림과 신고 조치 내역 알림 모두 존재하고, 게시글 내역 알림이 가장 최신인 경우 true를 반환한다.") + void returnsTrueWhenPostAlarmIsLatestAndReportActionAlarmIsNotLatest() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + Alarm alarm = alarmTestPersister.builder().member(member).save(); + ReportActionAlarm reportActionAlarm = reportActionAlarmTestPersister.builder().member(member).save(); + + ReflectionTestUtils.setField(member, "alarmCheckedAt", LocalDateTime.now().minusDays(1)); + ReflectionTestUtils.setField(alarm, "createdAt", LocalDateTime.now()); + ReflectionTestUtils.setField(reportActionAlarm, "createdAt", LocalDateTime.now().minusDays(1)); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertThat(memberInfoResponse.hasLatestAlarm()).isTrue(); + } + + @Test + @DisplayName("게시글 내역 알림과 신고 조치 내역 알림 모두 존재하고, 신고 조치 내역 알림이 가장 최신인 경우 true를 반환한다.") + void returnsTrueWhenPostAlarmIsNotLatestAndReportActionAlarmIsLatest() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + Alarm alarm = alarmTestPersister.builder().member(member).save(); + ReportActionAlarm reportActionAlarm = reportActionAlarmTestPersister.builder().member(member).save(); + + ReflectionTestUtils.setField(member, "alarmCheckedAt", LocalDateTime.now().minusDays(1)); + ReflectionTestUtils.setField(alarm, "createdAt", LocalDateTime.now().minusDays(1)); + ReflectionTestUtils.setField(reportActionAlarm, "createdAt", LocalDateTime.now()); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertThat(memberInfoResponse.hasLatestAlarm()).isTrue(); + } + @Test @DisplayName("신고 조치 내역 알림이 존재하지만, 회원의 최신 알림 확인 시각이 더 최신인 경우 false를 반환한다.") void returnsTrueWhenReportActionAlarmOrPostAlarmIsNotLatest() { From f4801f432a68a550292cbd66de2619e3cdaf4784 Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 11:38:54 +0900 Subject: [PATCH 09/12] =?UTF-8?q?chore:=20(#774)=20=EC=82=AC=EC=86=8C?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberControllerDocs.java | 3 +-- .../main/java/com/votogether/domain/member/entity/Member.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java b/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java index 7a0d600f5..58f09b1d9 100644 --- a/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java +++ b/backend/src/main/java/com/votogether/domain/member/controller/MemberControllerDocs.java @@ -23,8 +23,7 @@ public interface MemberControllerDocs { @ApiResponse( responseCode = "400", description = "회원에 해당하는 통계 정보가 없는 경우", - content = @Content(schema = @Schema(implementation = ExceptionResponse.class) - ) + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) ) }) ResponseEntity findMemberInfo(final Member member); diff --git a/backend/src/main/java/com/votogether/domain/member/entity/Member.java b/backend/src/main/java/com/votogether/domain/member/entity/Member.java index 8190739ad..a335ada2f 100644 --- a/backend/src/main/java/com/votogether/domain/member/entity/Member.java +++ b/backend/src/main/java/com/votogether/domain/member/entity/Member.java @@ -56,7 +56,7 @@ public class Member extends BaseEntity { private String socialId; @Enumerated(EnumType.STRING) - @Column(nullable = false, length = 20) + @Column(length = 20, nullable = false) private Roles roles; @Column(columnDefinition = "datetime(6)", nullable = false) From a2177b605650d784120c3a90cb07b1ada34e053b Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 11:47:19 +0900 Subject: [PATCH 10/12] =?UTF-8?q?test:=20(#774)=20=EB=AA=A8=EB=93=A0=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=EC=9D=B4=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=84=20=EB=95=8C=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=20=EC=95=8C=EB=A6=BC=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8A=94=20=EA=B2=80=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/entity/MemberTest.java | 5 +++-- .../domain/member/service/MemberServiceTest.java | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java index d571d29ca..0cdba4e65 100644 --- a/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java +++ b/backend/src/test/java/com/votogether/domain/member/entity/MemberTest.java @@ -114,10 +114,11 @@ void notAllowedChangeToInitialNicknamePrefix() { } @Test - @DisplayName("회원은 알림을 확인하면 이전에 저장되어있던 시간과는 다르다.") + @DisplayName("회원은 알림을 확인하면 이전에 저장되어있던 시간 이후의 시간으로 변경된다.") void checkAlarm() { // given Member member = MemberFixtures.MALE_20.get(); + ReflectionTestUtils.setField(member, "createdAt", LocalDateTime.of(2001, 10, 10, 12, 0)); LocalDateTime beforeAlarmCheckedAt = member.getAlarmCheckedAt(); // when @@ -125,7 +126,7 @@ void checkAlarm() { // then LocalDateTime afterAlarmCheckedAt = member.getAlarmCheckedAt(); - assertThat(beforeAlarmCheckedAt).isNotEqualTo(afterAlarmCheckedAt); + assertThat(afterAlarmCheckedAt).isAfter(beforeAlarmCheckedAt); } @Nested diff --git a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java index 7b6fc81f5..b341d4d23 100644 --- a/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java +++ b/backend/src/test/java/com/votogether/domain/member/service/MemberServiceTest.java @@ -291,6 +291,20 @@ void throwsExceptionWhenNoMetrics() { .hasMessage("메트릭 정보가 존재하지 않습니다."); } + @Test + @DisplayName("모든 알림이 존재하지 않는 경우 false가 반환된다.") + void returnsFalseWhenNoAlarmExists() { + // given + Member member = memberTestPersister.builder().save(); + memberMetricTestPersister.builder().member(member).save(); + + // when + MemberInfoResponse memberInfoResponse = memberService.findMemberInfo(member); + + // then + assertThat(memberInfoResponse.hasLatestAlarm()).isFalse(); + } + @Test @DisplayName("게시글 내역 알림이 존재한다면 회원의 최신 알림 확인 시각과 비교하여 게시글 알림이 더 최신인 경우 true를 반환한다.") void returnsTrueWhenPostAlarmIsLatest() { @@ -379,7 +393,7 @@ void returnsTrueWhenPostAlarmIsNotLatestAndReportActionAlarmIsLatest() { @Test @DisplayName("신고 조치 내역 알림이 존재하지만, 회원의 최신 알림 확인 시각이 더 최신인 경우 false를 반환한다.") - void returnsTrueWhenReportActionAlarmOrPostAlarmIsNotLatest() { + void returnsFalseWhenReportActionAlarmOrPostAlarmIsNotLatest() { // given Member member = memberTestPersister.builder().save(); memberMetricTestPersister.builder().member(member).save(); From 933b1e8abee3b491cfd59e7a675ac69c0fd890e9 Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 11:50:21 +0900 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20(#774)=20=EC=9D=B8=EB=8D=B1?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=EC=9D=84=20createdAt=EC=97=90=EC=84=9C=20id?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../votogether/domain/alarm/repository/AlarmRepository.java | 2 +- .../domain/alarm/repository/ReportActionAlarmRepository.java | 2 +- .../com/votogether/domain/member/service/MemberService.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java b/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java index 4968a4dde..c4ab2738f 100644 --- a/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java +++ b/backend/src/main/java/com/votogether/domain/alarm/repository/AlarmRepository.java @@ -14,6 +14,6 @@ public interface AlarmRepository extends JpaRepository { List findAllByMember(final Member member); - Optional findByMemberOrderByCreatedAtDesc(final Member member); + Optional findByMemberOrderByIdDesc(final Member member); } diff --git a/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java b/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java index 9a28827c8..c11433e92 100644 --- a/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java +++ b/backend/src/main/java/com/votogether/domain/alarm/repository/ReportActionAlarmRepository.java @@ -13,6 +13,6 @@ public interface ReportActionAlarmRepository extends JpaRepository findByIdAndMember(final Long Id, final Member member); - Optional findByMemberOrderByCreatedAtDesc(final Member member); + Optional findByMemberOrderByIdDesc(final Member member); } diff --git a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java index 89b97f53d..f6dc28dc2 100644 --- a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java +++ b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java @@ -91,9 +91,9 @@ public MemberInfoResponse findMemberInfo(final Member member) { } private boolean hasLatestAlarm(final Member member) { - final Optional maybeAlarm = alarmRepository.findByMemberOrderByCreatedAtDesc(member); + final Optional maybeAlarm = alarmRepository.findByMemberOrderByIdDesc(member); final Optional maybeReportActionAlarm = - reportActionAlarmRepository.findByMemberOrderByCreatedAtDesc(member); + reportActionAlarmRepository.findByMemberOrderByIdDesc(member); if (maybeAlarm.isEmpty() && maybeReportActionAlarm.isEmpty()) { return false; From 1a814de248bc349e7006d699daeee5339aa4352d Mon Sep 17 00:00:00 2001 From: jeomxon Date: Thu, 19 Oct 2023 12:20:32 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=20(#774)=20stream=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20=EC=95=8C=EB=A6=BC=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/service/MemberService.java | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java index f6dc28dc2..8a7059e5c 100644 --- a/backend/src/main/java/com/votogether/domain/member/service/MemberService.java +++ b/backend/src/main/java/com/votogether/domain/member/service/MemberService.java @@ -94,27 +94,22 @@ private boolean hasLatestAlarm(final Member member) { final Optional maybeAlarm = alarmRepository.findByMemberOrderByIdDesc(member); final Optional maybeReportActionAlarm = reportActionAlarmRepository.findByMemberOrderByIdDesc(member); + final List> maybeCreatedAts = List.of( + maybeAlarm.map(Alarm::getCreatedAt), + maybeReportActionAlarm.map(ReportActionAlarm::getCreatedAt) + ); - if (maybeAlarm.isEmpty() && maybeReportActionAlarm.isEmpty()) { - return false; - } - final LocalDateTime latestAlarmCreatedAt = getLatestAlarmCreatedAt(maybeAlarm, maybeReportActionAlarm); - return member.hasLatestAlarmCompareTo(latestAlarmCreatedAt); + return getLatestAlarmCreatedAt(maybeCreatedAts, member); } - private LocalDateTime getLatestAlarmCreatedAt( - final Optional maybeAlarm, - final Optional maybeReportActionAlarm + private boolean getLatestAlarmCreatedAt( + final List> maybeCreatedAts, + final Member member ) { - if (maybeAlarm.isEmpty()) { - return maybeReportActionAlarm.get().getCreatedAt(); - } - if (maybeReportActionAlarm.isEmpty()) { - return maybeAlarm.get().getCreatedAt(); - } - final Alarm alarm = maybeAlarm.get(); - final ReportActionAlarm reportActionAlarm = maybeReportActionAlarm.get(); - return alarm.getLatestAlarmCreatedAt(reportActionAlarm.getCreatedAt()); + return maybeCreatedAts.stream() + .filter(Optional::isPresent) + .map(Optional::get) + .anyMatch(member::hasLatestAlarmCompareTo); } @Transactional