diff --git a/module-domain/src/main/java/com/depromeet/blacklist/domain/vo/BlacklistPage.java b/module-domain/src/main/java/com/depromeet/blacklist/domain/vo/BlacklistPage.java new file mode 100644 index 00000000..1f0c493a --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/blacklist/domain/vo/BlacklistPage.java @@ -0,0 +1,10 @@ +package com.depromeet.blacklist.domain.vo; + +import com.depromeet.member.domain.Member; +import java.util.List; + +public record BlacklistPage(List blackMembers, boolean hasNext, Long cursorId) { + public static BlacklistPage of(List blackMembers, boolean hasNext, Long nextCursorId) { + return new BlacklistPage(blackMembers, hasNext, nextCursorId); + } +} diff --git a/module-domain/src/main/java/com/depromeet/blacklist/port/.gitkeep b/module-domain/src/main/java/com/depromeet/blacklist/port/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/module-domain/src/main/java/com/depromeet/blacklist/port/in/usecase/BlacklistCommandUseCase.java b/module-domain/src/main/java/com/depromeet/blacklist/port/in/usecase/BlacklistCommandUseCase.java new file mode 100644 index 00000000..162a9275 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/blacklist/port/in/usecase/BlacklistCommandUseCase.java @@ -0,0 +1,9 @@ +package com.depromeet.blacklist.port.in.usecase; + +import com.depromeet.blacklist.domain.Blacklist; + +public interface BlacklistCommandUseCase { + Blacklist blackMember(Long memberId, Long blackMemberId); + + void unblackMember(Long memberId, Long blackMemberId); +} diff --git a/module-domain/src/main/java/com/depromeet/blacklist/port/in/usecase/BlacklistQueryUseCase.java b/module-domain/src/main/java/com/depromeet/blacklist/port/in/usecase/BlacklistQueryUseCase.java new file mode 100644 index 00000000..8f71ae81 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/blacklist/port/in/usecase/BlacklistQueryUseCase.java @@ -0,0 +1,12 @@ +package com.depromeet.blacklist.port.in.usecase; + +import com.depromeet.blacklist.domain.vo.BlacklistPage; +import java.util.Set; + +public interface BlacklistQueryUseCase { + boolean checkBlackMember(Long memberId, Long blackMemberId); + + BlacklistPage getBlackMembers(Long memberId, Long cursorId); + + Set getBlackMemberIds(Long memberId); +} diff --git a/module-domain/src/main/java/com/depromeet/blacklist/port/out/persistence/BlacklistPersistencePort.java b/module-domain/src/main/java/com/depromeet/blacklist/port/out/persistence/BlacklistPersistencePort.java new file mode 100644 index 00000000..ba3a1825 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/blacklist/port/out/persistence/BlacklistPersistencePort.java @@ -0,0 +1,19 @@ +package com.depromeet.blacklist.port.out.persistence; + +import com.depromeet.blacklist.domain.Blacklist; +import com.depromeet.member.domain.Member; +import java.util.List; + +public interface BlacklistPersistencePort { + Blacklist save(Blacklist blacklist); + + boolean existsByMemberIdAndBlackMemberId(Long memberId, Long blackMemberId); + + void unblackMember(Long memberId, Long blackMemberId); + + List findBlackMembers(Long memberId, Long cursorId); + + List findBlackMemberIdsByMemberId(Long memberId); + + List findMemberIdsWhoBlockedMe(Long memberId); +} diff --git a/module-domain/src/main/java/com/depromeet/blacklist/service/.gitkeep b/module-domain/src/main/java/com/depromeet/blacklist/service/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/module-domain/src/main/java/com/depromeet/blacklist/service/BlacklistService.java b/module-domain/src/main/java/com/depromeet/blacklist/service/BlacklistService.java new file mode 100644 index 00000000..49ad31af --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/blacklist/service/BlacklistService.java @@ -0,0 +1,73 @@ +package com.depromeet.blacklist.service; + +import com.depromeet.blacklist.domain.Blacklist; +import com.depromeet.blacklist.domain.vo.BlacklistPage; +import com.depromeet.blacklist.port.in.usecase.BlacklistCommandUseCase; +import com.depromeet.blacklist.port.in.usecase.BlacklistQueryUseCase; +import com.depromeet.blacklist.port.out.persistence.BlacklistPersistencePort; +import com.depromeet.member.domain.Member; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class BlacklistService implements BlacklistQueryUseCase, BlacklistCommandUseCase { + private final BlacklistPersistencePort blacklistPersistencePort; + + @Override + @Transactional + public Blacklist blackMember(Long memberId, Long blackMemberId) { + return blacklistPersistencePort.save( + Blacklist.builder() + .member(Member.builder().id(memberId).build()) + .blackMember(Member.builder().id(blackMemberId).build()) + .build()); + } + + @Override + @Transactional + public void unblackMember(Long memberId, Long blackMemberId) { + blacklistPersistencePort.unblackMember(memberId, blackMemberId); + } + + @Override + public boolean checkBlackMember(Long memberId, Long blackMemberId) { + return blacklistPersistencePort.existsByMemberIdAndBlackMemberId(memberId, blackMemberId); + } + + @Override + public BlacklistPage getBlackMembers(Long memberId, Long cursorId) { + List blackMembers = blacklistPersistencePort.findBlackMembers(memberId, cursorId); + boolean hasNext = false; + Long nextCursorId = null; + if (blackMembers != null && blackMembers.size() > 10) { + hasNext = true; + Member member = blackMembers.removeLast(); + nextCursorId = member.getId(); + } + return BlacklistPage.of(blackMembers, hasNext, nextCursorId); + } + + @Override + public Set getBlackMemberIds(Long memberId) { + List blackMemberIds = getBlackMemberIdsByMemberId(memberId); + List memberIdsWhoBlockedMe = getMemberIdsWhoBlockedMe(memberId); + + blackMemberIds.addAll(memberIdsWhoBlockedMe); + + return new HashSet<>(blackMemberIds); + } + + private List getBlackMemberIdsByMemberId(Long memberId) { + return blacklistPersistencePort.findBlackMemberIdsByMemberId(memberId); + } + + private List getMemberIdsWhoBlockedMe(Long memberId) { + return blacklistPersistencePort.findMemberIdsWhoBlockedMe(memberId); + } +} diff --git a/module-domain/src/main/java/com/depromeet/friend/domain/vo/FollowSlice.java b/module-domain/src/main/java/com/depromeet/friend/domain/vo/FollowSlice.java index 21004f14..0ae0ee2c 100644 --- a/module-domain/src/main/java/com/depromeet/friend/domain/vo/FollowSlice.java +++ b/module-domain/src/main/java/com/depromeet/friend/domain/vo/FollowSlice.java @@ -10,14 +10,12 @@ @NoArgsConstructor public class FollowSlice { private List followContents; - private int pageSize; private Long cursorId; private boolean hasNext; @Builder - public FollowSlice(List followContents, int pageSize, Long cursorId, boolean hasNext) { + public FollowSlice(List followContents, Long cursorId, boolean hasNext) { this.followContents = followContents != null ? followContents : new ArrayList<>(); - this.pageSize = pageSize != 0 ? pageSize : 10; this.cursorId = cursorId; this.hasNext = hasNext; } diff --git a/module-domain/src/main/java/com/depromeet/friend/port/out/persistence/FriendPersistencePort.java b/module-domain/src/main/java/com/depromeet/friend/port/out/persistence/FriendPersistencePort.java index 9ce9f36a..f16c76c1 100644 --- a/module-domain/src/main/java/com/depromeet/friend/port/out/persistence/FriendPersistencePort.java +++ b/module-domain/src/main/java/com/depromeet/friend/port/out/persistence/FriendPersistencePort.java @@ -14,9 +14,9 @@ public interface FriendPersistencePort { void deleteByMemberIdAndFollowingId(Long memberId, Long followingId); - FollowSlice findFollowingsByMemberIdAndCursorId(Long memberId, Long cursorId); + List findFollowingsByMemberIdAndCursorId(Long memberId, Long cursorId); - FollowSlice findFollowersByMemberIdAndCursorId(Long memberId, Long cursorId); + List findFollowersByMemberIdAndCursorId(Long memberId, Long cursorId); int countFollowingByMemberId(Long memberId); diff --git a/module-domain/src/main/java/com/depromeet/friend/service/FollowService.java b/module-domain/src/main/java/com/depromeet/friend/service/FollowService.java index 5c66aeb4..67e37db5 100644 --- a/module-domain/src/main/java/com/depromeet/friend/service/FollowService.java +++ b/module-domain/src/main/java/com/depromeet/friend/service/FollowService.java @@ -7,6 +7,7 @@ import com.depromeet.friend.port.out.persistence.FriendPersistencePort; import com.depromeet.member.domain.Member; import com.depromeet.type.friend.FollowErrorType; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -39,12 +40,42 @@ public boolean addOrDeleteFollow(Member member, Member following) { @Override public FollowSlice getFollowingByMemberIdAndCursorId(Long memberId, Long cursorId) { - return friendPersistencePort.findFollowingsByMemberIdAndCursorId(memberId, cursorId); + List followings = + friendPersistencePort.findFollowingsByMemberIdAndCursorId(memberId, cursorId); + + boolean hasNext = false; + Long nextCursorId = null; + if (followings.size() > 10) { + followings = new ArrayList<>(followings); + followings.removeLast(); + hasNext = true; + nextCursorId = followings.getLast().getFriendId(); + } + return FollowSlice.builder() + .followContents(followings) + .cursorId(nextCursorId) + .hasNext(hasNext) + .build(); } @Override public FollowSlice getFollowerByMemberIdAndCursorId(Long memberId, Long cursorId) { - return friendPersistencePort.findFollowersByMemberIdAndCursorId(memberId, cursorId); + List followers = + friendPersistencePort.findFollowersByMemberIdAndCursorId(memberId, cursorId); + + boolean hasNext = false; + Long nextCursorId = null; + if (followers.size() > 10) { + followers = new ArrayList<>(followers); + followers.removeLast(); + hasNext = true; + nextCursorId = followers.getLast().getFriendId(); + } + return FollowSlice.builder() + .followContents(followers) + .cursorId(nextCursorId) + .hasNext(hasNext) + .build(); } @Override diff --git a/module-domain/src/main/java/com/depromeet/member/domain/Member.java b/module-domain/src/main/java/com/depromeet/member/domain/Member.java index 83dd5162..834b41c0 100644 --- a/module-domain/src/main/java/com/depromeet/member/domain/Member.java +++ b/module-domain/src/main/java/com/depromeet/member/domain/Member.java @@ -43,6 +43,13 @@ public Member( this.lastViewedFollowingLogAt = lastViewedFollowingLogAt; } + public Member(Long id, String nickname, String profileImageUrl, String introduction) { + this.id = id; + this.nickname = nickname; + this.profileImageUrl = profileImageUrl; + this.introduction = introduction; + } + public Member updateGoal(Integer goal) { this.goal = goal; return this; diff --git a/module-domain/src/main/java/com/depromeet/member/domain/vo/MemberIdAndNickname.java b/module-domain/src/main/java/com/depromeet/member/domain/vo/MemberIdAndNickname.java new file mode 100644 index 00000000..43ac5356 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/member/domain/vo/MemberIdAndNickname.java @@ -0,0 +1,3 @@ +package com.depromeet.member.domain.vo; + +public record MemberIdAndNickname(Long id, String nickname) {} diff --git a/module-domain/src/main/java/com/depromeet/member/port/in/usecase/MemberUseCase.java b/module-domain/src/main/java/com/depromeet/member/port/in/usecase/MemberUseCase.java index 758ac4a6..cd7b6452 100644 --- a/module-domain/src/main/java/com/depromeet/member/port/in/usecase/MemberUseCase.java +++ b/module-domain/src/main/java/com/depromeet/member/port/in/usecase/MemberUseCase.java @@ -1,6 +1,7 @@ package com.depromeet.member.port.in.usecase; import com.depromeet.member.domain.Member; +import com.depromeet.member.domain.vo.MemberIdAndNickname; import com.depromeet.member.domain.vo.MemberSearchPage; import com.depromeet.member.port.in.command.SocialMemberCommand; @@ -16,4 +17,6 @@ public interface MemberUseCase { Member findByProviderId(String providerId); Member createMemberBy(SocialMemberCommand command); + + MemberIdAndNickname findIdAndNicknameById(Long memberId); } diff --git a/module-domain/src/main/java/com/depromeet/member/port/out/persistence/MemberPersistencePort.java b/module-domain/src/main/java/com/depromeet/member/port/out/persistence/MemberPersistencePort.java index c74f9542..d98f298a 100644 --- a/module-domain/src/main/java/com/depromeet/member/port/out/persistence/MemberPersistencePort.java +++ b/module-domain/src/main/java/com/depromeet/member/port/out/persistence/MemberPersistencePort.java @@ -2,6 +2,7 @@ import com.depromeet.member.domain.Member; import com.depromeet.member.domain.MemberGender; +import com.depromeet.member.domain.vo.MemberIdAndNickname; import com.depromeet.member.domain.vo.MemberSearchPage; import com.depromeet.member.port.in.command.UpdateMemberCommand; import java.util.Optional; @@ -30,4 +31,6 @@ public interface MemberPersistencePort { Optional update(UpdateMemberCommand command); Optional updateProfileImageUrl(Long memberId, String profileImageUrl); + + Optional findIdAndNicknameById(Long memberId); } diff --git a/module-domain/src/main/java/com/depromeet/member/service/MemberService.java b/module-domain/src/main/java/com/depromeet/member/service/MemberService.java index cda990f7..94b42d96 100644 --- a/module-domain/src/main/java/com/depromeet/member/service/MemberService.java +++ b/module-domain/src/main/java/com/depromeet/member/service/MemberService.java @@ -7,6 +7,7 @@ import com.depromeet.member.domain.Member; import com.depromeet.member.domain.MemberGender; import com.depromeet.member.domain.MemberRole; +import com.depromeet.member.domain.vo.MemberIdAndNickname; import com.depromeet.member.domain.vo.MemberSearchPage; import com.depromeet.member.port.in.command.SocialMemberCommand; import com.depromeet.member.port.in.command.UpdateMemberCommand; @@ -82,6 +83,13 @@ public Member createMemberBy(SocialMemberCommand command) { return memberPersistencePort.save(member); } + @Override + public MemberIdAndNickname findIdAndNicknameById(Long memberId) { + return memberPersistencePort + .findIdAndNicknameById(memberId) + .orElseThrow(() -> new NotFoundException(MemberErrorType.NOT_FOUND)); + } + @Override public Member update(UpdateMemberCommand command) { if (command.nickname() != null && command.nickname().isBlank()) { diff --git a/module-domain/src/main/java/com/depromeet/memory/domain/vo/MemoryIdAndDiaryAndMember.java b/module-domain/src/main/java/com/depromeet/memory/domain/vo/MemoryIdAndDiaryAndMember.java new file mode 100644 index 00000000..e1d6cc12 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/memory/domain/vo/MemoryIdAndDiaryAndMember.java @@ -0,0 +1,5 @@ +package com.depromeet.memory.domain.vo; + +import com.depromeet.member.domain.vo.MemberIdAndNickname; + +public record MemoryIdAndDiaryAndMember(Long id, String diary, MemberIdAndNickname member) {} diff --git a/module-domain/src/main/java/com/depromeet/memory/port/in/usecase/GetMemoryUseCase.java b/module-domain/src/main/java/com/depromeet/memory/port/in/usecase/GetMemoryUseCase.java index 4507b440..a29c294e 100644 --- a/module-domain/src/main/java/com/depromeet/memory/port/in/usecase/GetMemoryUseCase.java +++ b/module-domain/src/main/java/com/depromeet/memory/port/in/usecase/GetMemoryUseCase.java @@ -2,6 +2,7 @@ import com.depromeet.memory.domain.Memory; import com.depromeet.memory.domain.vo.MemoryAndDetailId; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; import com.depromeet.memory.domain.vo.MemoryInfo; import java.util.List; @@ -17,4 +18,6 @@ public interface GetMemoryUseCase { MemoryInfo findByIdWithPrevNext(Long requestMemberId, Long memoryId); MemoryAndDetailId findMemoryAndDetailIdsByMemberId(Long memberId); + + MemoryIdAndDiaryAndMember findIdAndNicknameById(Long memberId); } diff --git a/module-domain/src/main/java/com/depromeet/memory/port/out/persistence/MemoryPersistencePort.java b/module-domain/src/main/java/com/depromeet/memory/port/out/persistence/MemoryPersistencePort.java index 439c020d..57611dd0 100644 --- a/module-domain/src/main/java/com/depromeet/memory/port/out/persistence/MemoryPersistencePort.java +++ b/module-domain/src/main/java/com/depromeet/memory/port/out/persistence/MemoryPersistencePort.java @@ -2,6 +2,7 @@ import com.depromeet.memory.domain.Memory; import com.depromeet.memory.domain.vo.MemoryAndDetailId; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; import java.time.LocalDate; import java.util.List; import java.util.Optional; @@ -36,4 +37,6 @@ public interface MemoryPersistencePort { MemoryAndDetailId findMemoryAndDetailIdsByMemberId(Long memberId); void deleteById(Long memoryId); + + Optional findIdAndNicknameById(Long memberId); } diff --git a/module-domain/src/main/java/com/depromeet/memory/service/MemoryService.java b/module-domain/src/main/java/com/depromeet/memory/service/MemoryService.java index b1a1ce3a..435a0f04 100644 --- a/module-domain/src/main/java/com/depromeet/memory/service/MemoryService.java +++ b/module-domain/src/main/java/com/depromeet/memory/service/MemoryService.java @@ -10,6 +10,7 @@ import com.depromeet.memory.domain.MemoryDetail; import com.depromeet.memory.domain.Stroke; import com.depromeet.memory.domain.vo.MemoryAndDetailId; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; import com.depromeet.memory.domain.vo.MemoryInfo; import com.depromeet.memory.port.in.command.CreateMemoryCommand; import com.depromeet.memory.port.in.command.UpdateMemoryCommand; @@ -110,6 +111,13 @@ public MemoryAndDetailId findMemoryAndDetailIdsByMemberId(Long memberId) { return memoryPersistencePort.findMemoryAndDetailIdsByMemberId(memberId); } + @Override + public MemoryIdAndDiaryAndMember findIdAndNicknameById(Long memberId) { + return memoryPersistencePort + .findIdAndNicknameById(memberId) + .orElseThrow(() -> new NotFoundException(MemoryErrorType.NOT_FOUND)); + } + @Override @Transactional public Memory update(Long memoryId, UpdateMemoryCommand command, List strokes) { diff --git a/module-domain/src/main/java/com/depromeet/report/domain/ReportReasonCode.java b/module-domain/src/main/java/com/depromeet/report/domain/ReportReasonCode.java new file mode 100644 index 00000000..de630aec --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/report/domain/ReportReasonCode.java @@ -0,0 +1,30 @@ +package com.depromeet.report.domain; + +import com.depromeet.converter.AbstractCodedEnumConverter; +import com.depromeet.converter.CodedEnum; + +public enum ReportReasonCode implements CodedEnum { + REPORT_REASON_1("스팸, 광고"), + REPORT_REASON_2("폭력적인 발언"), + REPORT_REASON_3("음란성, 선정 내용"), + REPORT_REASON_4("개인정보 노출"), + REPORT_REASON_5("주제와 무관"); + + private String value; + + ReportReasonCode(String value) { + this.value = value; + } + + @Override + public String getValue() { + return this.value; + } + + @jakarta.persistence.Converter(autoApply = true) + static class Converter extends AbstractCodedEnumConverter { + public Converter() { + super(ReportReasonCode.class); + } + } +} diff --git a/module-domain/src/main/java/com/depromeet/report/port/in/command/CreateReportCommand.java b/module-domain/src/main/java/com/depromeet/report/port/in/command/CreateReportCommand.java new file mode 100644 index 00000000..a0b8d690 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/report/port/in/command/CreateReportCommand.java @@ -0,0 +1,13 @@ +package com.depromeet.report.port.in.command; + +import com.depromeet.image.domain.Image; +import com.depromeet.member.domain.vo.MemberIdAndNickname; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; +import com.depromeet.report.domain.ReportReasonCode; +import java.util.List; + +public record CreateReportCommand( + MemberIdAndNickname member, + MemoryIdAndDiaryAndMember reportMemory, + List images, + ReportReasonCode reasonCode) {} diff --git a/module-domain/src/main/java/com/depromeet/report/port/in/usecase/CreateReportUseCase.java b/module-domain/src/main/java/com/depromeet/report/port/in/usecase/CreateReportUseCase.java new file mode 100644 index 00000000..7f04899f --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/report/port/in/usecase/CreateReportUseCase.java @@ -0,0 +1,7 @@ +package com.depromeet.report.port.in.usecase; + +import com.depromeet.report.port.in.command.CreateReportCommand; + +public interface CreateReportUseCase { + void save(CreateReportCommand command); +} diff --git a/module-domain/src/main/java/com/depromeet/report/port/out/persistence/ReportPersistencePort.java b/module-domain/src/main/java/com/depromeet/report/port/out/persistence/ReportPersistencePort.java new file mode 100644 index 00000000..b7ee0f4f --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/report/port/out/persistence/ReportPersistencePort.java @@ -0,0 +1,7 @@ +package com.depromeet.report.port.out.persistence; + +import com.depromeet.report.port.in.command.CreateReportCommand; + +public interface ReportPersistencePort { + void writeReportToSheet(CreateReportCommand command); +} diff --git a/module-domain/src/main/java/com/depromeet/report/service/ReportService.java b/module-domain/src/main/java/com/depromeet/report/service/ReportService.java new file mode 100644 index 00000000..e1cb4428 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/report/service/ReportService.java @@ -0,0 +1,18 @@ +package com.depromeet.report.service; + +import com.depromeet.report.port.in.command.CreateReportCommand; +import com.depromeet.report.port.in.usecase.CreateReportUseCase; +import com.depromeet.report.port.out.persistence.ReportPersistencePort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReportService implements CreateReportUseCase { + private final ReportPersistencePort reportPersistencePort; + + @Override + public void save(CreateReportCommand command) { + reportPersistencePort.writeReportToSheet(command); + } +} diff --git a/module-domain/src/test/java/com/depromeet/mock/friend/FakeFriendRepository.java b/module-domain/src/test/java/com/depromeet/mock/friend/FakeFriendRepository.java index 6748a537..dbe16811 100644 --- a/module-domain/src/test/java/com/depromeet/mock/friend/FakeFriendRepository.java +++ b/module-domain/src/test/java/com/depromeet/mock/friend/FakeFriendRepository.java @@ -81,44 +81,22 @@ public void deleteByMemberIdAndFollowingId(Long memberId, Long followingId) { } @Override - public FollowSlice findFollowingsByMemberIdAndCursorId( - Long memberId, Long cursorId) { - List followings = - friends.stream() - .filter( - item -> - item.getMember().getId().equals(memberId) - && ltCursorId(cursorId, item)) - .map( - item -> - Following.builder() - .friendId(item.getId()) - .memberId(item.getFollowing().getId()) - .name(item.getFollowing().getNickname()) - .profileImageUrl( - item.getFollowing().getProfileImageUrl()) - .introduction(item.getFollowing().getIntroduction()) - .build()) - .toList(); - - followings = new ArrayList<>(followings); - - followings.sort( - (follow1, follow2) -> follow2.getFriendId().compareTo(follow1.getFriendId())); - - boolean hasNext = false; - Long nextCursorId = null; - if (followings.size() > 10) { - followings.removeLast(); - hasNext = true; - nextCursorId = followings.getLast().getFriendId(); - } - return FollowSlice.builder() - .followContents(followings) - .pageSize(followings.size()) - .cursorId(nextCursorId) - .hasNext(hasNext) - .build(); + public List findFollowingsByMemberIdAndCursorId(Long memberId, Long cursorId) { + return friends.stream() + .filter( + item -> + item.getMember().getId().equals(memberId) + && ltCursorId(cursorId, item)) + .map( + item -> + Following.builder() + .friendId(item.getId()) + .memberId(item.getFollowing().getId()) + .name(item.getFollowing().getNickname()) + .profileImageUrl(item.getFollowing().getProfileImageUrl()) + .introduction(item.getFollowing().getIntroduction()) + .build()) + .toList(); } public boolean ltCursorId(Long cursorId, Friend friend) { @@ -127,43 +105,22 @@ public boolean ltCursorId(Long cursorId, Friend friend) { } @Override - public FollowSlice findFollowersByMemberIdAndCursorId(Long memberId, Long cursorId) { - List followers = - friends.stream() - .filter( - item -> - item.getFollowing().getId().equals(memberId) - && ltCursorId(cursorId, item)) - .map( - item -> - Follower.builder() - .friendId(item.getId()) - .memberId(item.getMember().getId()) - .name(item.getMember().getNickname()) - .profileImageUrl( - item.getMember().getProfileImageUrl()) - .introduction(item.getMember().getIntroduction()) - .build()) - .toList(); - - followers = new ArrayList<>(followers); - - followers.sort( - (follow1, follow2) -> follow2.getFriendId().compareTo(follow1.getFriendId())); - - boolean hasNext = false; - Long nextCursorId = null; - if (followers.size() > 10) { - followers.removeLast(); - hasNext = true; - nextCursorId = followers.getLast().getFriendId(); - } - return FollowSlice.builder() - .followContents(followers) - .pageSize(followers.size()) - .cursorId(nextCursorId) - .hasNext(hasNext) - .build(); + public List findFollowersByMemberIdAndCursorId(Long memberId, Long cursorId) { + return friends.stream() + .filter( + item -> + item.getFollowing().getId().equals(memberId) + && ltCursorId(cursorId, item)) + .map( + item -> + Follower.builder() + .friendId(item.getId()) + .memberId(item.getMember().getId()) + .name(item.getMember().getNickname()) + .profileImageUrl(item.getMember().getProfileImageUrl()) + .introduction(item.getMember().getIntroduction()) + .build()) + .toList(); } @Override diff --git a/module-domain/src/test/java/com/depromeet/mock/member/FakeMemberRepository.java b/module-domain/src/test/java/com/depromeet/mock/member/FakeMemberRepository.java index 4f4aad7b..0a46d5a2 100644 --- a/module-domain/src/test/java/com/depromeet/mock/member/FakeMemberRepository.java +++ b/module-domain/src/test/java/com/depromeet/mock/member/FakeMemberRepository.java @@ -2,6 +2,7 @@ import com.depromeet.member.domain.Member; import com.depromeet.member.domain.MemberGender; +import com.depromeet.member.domain.vo.MemberIdAndNickname; import com.depromeet.member.domain.vo.MemberSearchPage; import com.depromeet.member.port.in.command.UpdateMemberCommand; import com.depromeet.member.port.out.persistence.MemberPersistencePort; @@ -122,4 +123,10 @@ public Optional updateProfileImageUrl(Long memberId, String profileImage return member; }); } + + @Override + public Optional findIdAndNicknameById(Long memberId) { + return findById(memberId) + .map(item -> new MemberIdAndNickname(item.getId(), item.getNickname())); + } } diff --git a/module-domain/src/test/java/com/depromeet/mock/memory/FakeMemoryRepository.java b/module-domain/src/test/java/com/depromeet/mock/memory/FakeMemoryRepository.java index e1c123f9..137f8754 100644 --- a/module-domain/src/test/java/com/depromeet/mock/memory/FakeMemoryRepository.java +++ b/module-domain/src/test/java/com/depromeet/mock/memory/FakeMemoryRepository.java @@ -1,7 +1,9 @@ package com.depromeet.mock.memory; +import com.depromeet.member.domain.vo.MemberIdAndNickname; import com.depromeet.memory.domain.Memory; import com.depromeet.memory.domain.vo.MemoryAndDetailId; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; import com.depromeet.memory.port.out.persistence.MemoryPersistencePort; import java.time.LocalDate; import java.util.ArrayList; @@ -194,4 +196,17 @@ public MemoryAndDetailId findMemoryAndDetailIdsByMemberId(Long memberId) { public void deleteById(Long memoryId) { data.removeIf(item -> item.getId().equals(memoryId)); } + + @Override + public Optional findIdAndNicknameById(Long memberId) { + return findById(memberId) + .map( + item -> + new MemoryIdAndDiaryAndMember( + item.getId(), + item.getDiary(), + new MemberIdAndNickname( + item.getMember().getId(), + item.getMember().getNickname()))); + } } diff --git a/module-independent/src/main/java/com/depromeet/type/blacklist/BlacklistErrorType.java b/module-independent/src/main/java/com/depromeet/type/blacklist/BlacklistErrorType.java new file mode 100644 index 00000000..41ee3b99 --- /dev/null +++ b/module-independent/src/main/java/com/depromeet/type/blacklist/BlacklistErrorType.java @@ -0,0 +1,26 @@ +package com.depromeet.type.blacklist; + +import com.depromeet.type.ErrorType; + +public enum BlacklistErrorType implements ErrorType { + ALREADY_BLACKED("BLACK_1", "이미 차단한 사용자입니다"), + CANNOT_BLACK_MYSELF("BLACK_2", "자기 자신을 차단할 수 없습니다"); + + private final String code; + private final String message; + + BlacklistErrorType(String code, String message) { + this.code = code; + this.message = message; + } + + @Override + public String getCode() { + return this.code; + } + + @Override + public String getMessage() { + return this.message; + } +} diff --git a/module-independent/src/main/java/com/depromeet/type/blacklist/BlacklistSuccessType.java b/module-independent/src/main/java/com/depromeet/type/blacklist/BlacklistSuccessType.java new file mode 100644 index 00000000..8351a106 --- /dev/null +++ b/module-independent/src/main/java/com/depromeet/type/blacklist/BlacklistSuccessType.java @@ -0,0 +1,27 @@ +package com.depromeet.type.blacklist; + +import com.depromeet.type.SuccessType; + +public enum BlacklistSuccessType implements SuccessType { + BLACK_MEMBER_SUCCESS("BLACK_1", "사용자를 성공적으로 차단하였습니다"), + UNBLACK_MEMBER_SUCCESS("BLACK_2", "사용자를 차단 해제하였습니다"), + GET_BLACK_MEMBERS_SUCCESS("BLACK_3", "사용자의 차단 목록을 성공적으로 조회하였습니다"); + + private final String code; + private final String message; + + BlacklistSuccessType(String code, String message) { + this.code = code; + this.message = message; + } + + @Override + public String getCode() { + return this.code; + } + + @Override + public String getMessage() { + return this.message; + } +} diff --git a/module-independent/src/main/java/com/depromeet/type/friend/FollowErrorType.java b/module-independent/src/main/java/com/depromeet/type/friend/FollowErrorType.java index caf99679..b21f802d 100644 --- a/module-independent/src/main/java/com/depromeet/type/friend/FollowErrorType.java +++ b/module-independent/src/main/java/com/depromeet/type/friend/FollowErrorType.java @@ -8,7 +8,6 @@ public enum FollowErrorType implements ErrorType { INVALID_FOLLOW_TYPE("FOLLOW_3", "올바르지 않은 팔로우 타입입니다"); private final String code; - private final String message; FollowErrorType(String code, String message) { diff --git a/module-independent/src/main/java/com/depromeet/type/report/ReportErrorType.java b/module-independent/src/main/java/com/depromeet/type/report/ReportErrorType.java new file mode 100644 index 00000000..bccceba6 --- /dev/null +++ b/module-independent/src/main/java/com/depromeet/type/report/ReportErrorType.java @@ -0,0 +1,25 @@ +package com.depromeet.type.report; + +import com.depromeet.type.ErrorType; + +public enum ReportErrorType implements ErrorType { + CANNOT_REPORT_OWN_MEMORY("REPORT_1", "자신의 기록을 신고할 수 없습니다"); + + private final String code; + private final String message; + + ReportErrorType(String code, String message) { + this.code = code; + this.message = message; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-independent/src/main/java/com/depromeet/type/report/ReportSuccessType.java b/module-independent/src/main/java/com/depromeet/type/report/ReportSuccessType.java new file mode 100644 index 00000000..1cd08f93 --- /dev/null +++ b/module-independent/src/main/java/com/depromeet/type/report/ReportSuccessType.java @@ -0,0 +1,25 @@ +package com.depromeet.type.report; + +import com.depromeet.type.SuccessType; + +public enum ReportSuccessType implements SuccessType { + POST_RESULT_SUCCESS("REPORT_1", "신고 사유 등록에 성공하였습니다"); + + private final String code; + private final String message; + + ReportSuccessType(String code, String message) { + this.code = code; + this.message = message; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/config/SpreadSheetProperties.java b/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/config/SpreadSheetProperties.java index 71bd12a2..6ddab98d 100644 --- a/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/config/SpreadSheetProperties.java +++ b/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/config/SpreadSheetProperties.java @@ -4,4 +4,10 @@ @ConfigurationProperties(prefix = "google.sheet") public record SpreadSheetProperties( - String credentialsFilePath, String applicationName, String sheetId, String range) {} + String credentialsFilePath, + String applicationName, + String sheetId, + String range, + String reportApplicationName, + String reportSheetId, + String reportRange) {} diff --git a/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/spreadsheet/GoogleSheetManager.java b/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/spreadsheet/GoogleSheetManager.java index e110a72e..e9446f35 100644 --- a/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/spreadsheet/GoogleSheetManager.java +++ b/module-infrastructure/google-spreadsheet/src/main/java/com/depromeet/spreadsheet/GoogleSheetManager.java @@ -4,6 +4,9 @@ import com.depromeet.config.SpreadSheetProperties; import com.depromeet.exception.InternalServerException; +import com.depromeet.image.domain.Image; +import com.depromeet.report.port.in.command.CreateReportCommand; +import com.depromeet.report.port.out.persistence.ReportPersistencePort; import com.depromeet.withdrawal.domain.ReasonType; import com.depromeet.withdrawal.port.out.persistence.WithdrawalReasonPort; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; @@ -25,21 +28,25 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Slf4j @Component @RequiredArgsConstructor -public class GoogleSheetManager implements WithdrawalReasonPort { +public class GoogleSheetManager implements WithdrawalReasonPort, ReportPersistencePort { private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final List SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS); private final SpreadSheetProperties spreadSheetProperties; + @Value("${cloud-front.domain}") + private String domain; + @Override public void writeToSheet(ReasonType reasonType, String feedback) { try { - Sheets sheet = getSheetService(); + Sheets sheet = getSheetService(spreadSheetProperties.applicationName()); List> data = getData(reasonType, feedback); ValueRange valueRange = new ValueRange().setValues(data); @@ -60,7 +67,8 @@ public void writeToSheet(ReasonType reasonType, String feedback) { } } - private Sheets getSheetService() throws IOException, GeneralSecurityException { + private Sheets getSheetService(String sheetApplicationName) + throws IOException, GeneralSecurityException { GoogleCredentials googleCredentials = GoogleCredentials.fromStream( new FileInputStream(spreadSheetProperties.credentialsFilePath())) @@ -69,17 +77,73 @@ private Sheets getSheetService() throws IOException, GeneralSecurityException { GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY, new HttpCredentialsAdapter(googleCredentials)) - .setApplicationName(spreadSheetProperties.applicationName()) + .setApplicationName(sheetApplicationName) .build(); } private List> getData(ReasonType reasonType, String feedback) { - String date = - LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + String date = getDateTimeNow(); feedback = feedback != null ? feedback : ""; List> data = new ArrayList<>(); data.add(List.of(reasonType.getCode(), reasonType.getReason(), feedback, date)); return data; } + + @Override + public void writeReportToSheet(CreateReportCommand command) { + try { + Sheets sheet = getSheetService(spreadSheetProperties.reportApplicationName()); + List> data = getReportData(command); + ValueRange valueRange = new ValueRange().setValues(data); + + AppendValuesResponse appendResult = + sheet.spreadsheets() + .values() + .append( + spreadSheetProperties.reportSheetId(), + spreadSheetProperties.reportRange(), + valueRange) + .setValueInputOption("RAW") + .setInsertDataOption("INSERT_ROWS") + .setIncludeValuesInResponse(true) + .execute(); + } catch (Exception e) { + log.error("Error writing to sheet", e); + throw new InternalServerException(FAILED_TO_INSERT_DATA_TO_SPREADSHEET); + } + } + + private List> getReportData(CreateReportCommand command) { + String date = getDateTimeNow(); + String diary = command.reportMemory().diary() == null ? "" : command.reportMemory().diary(); + + List> data = new ArrayList<>(); + data.add( + List.of( + command.member().id(), + command.member().nickname(), + command.reportMemory().member().id(), + command.reportMemory().member().nickname(), + command.reportMemory().id(), + getImagesUrl(command.images()), + diary, + command.reasonCode().name(), + command.reasonCode().getValue(), + date)); + return data; + } + + private static String getDateTimeNow() { + return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + private String getImagesUrl(List images) { + return images.isEmpty() + ? "" + : images.stream() + .map(image -> domain + "/" + image.getImageName()) + .toList() + .toString(); + } } diff --git a/module-infrastructure/google-spreadsheet/src/main/resources/application-spreadsheet.yml b/module-infrastructure/google-spreadsheet/src/main/resources/application-spreadsheet.yml index 4382beed..274e063a 100644 --- a/module-infrastructure/google-spreadsheet/src/main/resources/application-spreadsheet.yml +++ b/module-infrastructure/google-spreadsheet/src/main/resources/application-spreadsheet.yml @@ -9,3 +9,6 @@ google: application-name: ${GOOGLE_SHEET_APPLICATION_NAME} sheet-id: ${GOOGLE_SHEET_ID} range: ${GOOGLE_SHEET_RANGE} + report-application-name: ${GOOGLE_SHEET_REPORT_APPLICATION_NAME} + report-sheet-id: ${GOOGLE_SHEET_REPORT_ID} + report-range: ${GOOGLE_SHEET_REPORT_RANGE} diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/.gitkeep b/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/BlacklistJpaRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/BlacklistJpaRepository.java new file mode 100644 index 00000000..c99a7164 --- /dev/null +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/BlacklistJpaRepository.java @@ -0,0 +1,10 @@ +package com.depromeet.blacklist.repository; + +import com.depromeet.blacklist.entity.BlacklistEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BlacklistJpaRepository extends JpaRepository { + boolean existsByMemberIdAndBlackMemberId(Long memberId, Long blackMemberId); + + void deleteByMemberIdAndBlackMemberId(Long memberId, Long blackMemberId); +} diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/BlacklistRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/BlacklistRepository.java new file mode 100644 index 00000000..31778233 --- /dev/null +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/blacklist/repository/BlacklistRepository.java @@ -0,0 +1,91 @@ +package com.depromeet.blacklist.repository; + +import static com.depromeet.blacklist.entity.QBlacklistEntity.*; +import static com.depromeet.blacklist.entity.QBlacklistEntity.blacklistEntity; + +import com.depromeet.blacklist.domain.Blacklist; +import com.depromeet.blacklist.entity.BlacklistEntity; +import com.depromeet.blacklist.port.out.persistence.BlacklistPersistencePort; +import com.depromeet.member.domain.Member; +import com.depromeet.member.entity.QMemberEntity; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class BlacklistRepository implements BlacklistPersistencePort { + private final JPAQueryFactory queryFactory; + private final BlacklistJpaRepository blacklistJpaRepository; + + @Override + public Blacklist save(Blacklist blacklist) { + return blacklistJpaRepository.save(BlacklistEntity.from(blacklist)).toModel(); + } + + @Override + public boolean existsByMemberIdAndBlackMemberId(Long memberId, Long blackMemberId) { + return blacklistJpaRepository.existsByMemberIdAndBlackMemberId(memberId, blackMemberId); + } + + @Override + public void unblackMember(Long memberId, Long blackMemberId) { + blacklistJpaRepository.deleteByMemberIdAndBlackMemberId(memberId, blackMemberId); + } + + @Override + public List findBlackMembers(Long memberId, Long cursorId) { + QMemberEntity member = new QMemberEntity("member"); // 첫 번째 별칭 "member" + QMemberEntity blackMember = new QMemberEntity("blackMember"); + + return queryFactory + .select( + Projections.constructor( + Member.class, // 원하는 DTO나 엔티티 클래스 지정 + blacklistEntity.blackMember.id, + blacklistEntity.blackMember.nickname, + blacklistEntity.blackMember.profileImageUrl, + blacklistEntity.blackMember.introduction)) + .from(blacklistEntity) + .join(blacklistEntity.blackMember, blackMember) + .where(memberEq(memberId), blacklistIdLoe(cursorId)) + .orderBy(blacklistEntity.blackMember.id.desc()) + .limit(11) + .fetch(); + } + + private static BooleanExpression memberEq(Long memberId) { + if (memberId == null) { + return null; + } + return blacklistEntity.member.id.eq(memberId); + } + + private BooleanExpression blacklistIdLoe(Long cursorId) { + if (cursorId == null) { + return null; + } + return blacklistEntity.blackMember.id.loe(cursorId); + } + + @Override + public List findBlackMemberIdsByMemberId(Long memberId) { + return queryFactory + .select(blacklistEntity.blackMember.id) + .from(blacklistEntity) + .where(blacklistEntity.member.id.eq(memberId)) + .fetch(); + } + + @Override + public List findMemberIdsWhoBlockedMe(Long memberId) { + return queryFactory + .select(blacklistEntity.member.id) + .from(blacklistEntity) + .where(blacklistEntity.blackMember.id.eq(memberId)) + .fetch(); + } +} diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/friend/repository/FriendRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/friend/repository/FriendRepository.java index 1c453d13..5a1ebd5b 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/friend/repository/FriendRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/friend/repository/FriendRepository.java @@ -16,7 +16,6 @@ import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -71,82 +70,48 @@ public void deleteByMemberIdAndFollowingId(Long memberId, Long followingId) { } @Override - public FollowSlice findFollowingsByMemberIdAndCursorId( - Long memberId, Long cursorId) { - List content = - queryFactory - .select( - Projections.constructor( - Following.class, - friend.id.as("friendId"), - friend.following.id.as("memberId"), - friend.following.nickname.as("name"), - friend.following.profileImageUrl.as("profileImageUrl"), - friend.following.introduction.as("introduction"))) - .from(friend) - .where(friend.member.id.eq(memberId), ltCursorId(cursorId)) - .limit(11) - .orderBy(friend.id.desc()) - .fetch(); - - boolean hasNext = false; - Long nextCursorId = null; - if (content.size() > 10) { - content = new ArrayList<>(content); - content.removeLast(); - hasNext = true; - nextCursorId = content.getLast().getFriendId(); - } - return FollowSlice.builder() - .followContents(content) - .pageSize(content.size()) - .cursorId(nextCursorId) - .hasNext(hasNext) - .build(); + public List findFollowingsByMemberIdAndCursorId(Long memberId, Long cursorId) { + return queryFactory + .select( + Projections.constructor( + Following.class, + friend.id.as("friendId"), + friend.following.id.as("memberId"), + friend.following.nickname.as("name"), + friend.following.profileImageUrl.as("profileImageUrl"), + friend.following.introduction.as("introduction"))) + .from(friend) + .where(friend.member.id.eq(memberId), ltCursorId(cursorId)) + .limit(11) + .orderBy(friend.id.desc()) + .fetch(); } @Override - public FollowSlice findFollowersByMemberIdAndCursorId(Long memberId, Long cursorId) { + public List findFollowersByMemberIdAndCursorId(Long memberId, Long cursorId) { QFriendEntity subFriend = new QFriendEntity("sub"); - List result = - queryFactory - .select( - Projections.constructor( - Follower.class, - friend.id.as("friendId"), - friend.member.id.as("memberId"), - friend.member.nickname.as("name"), - friend.member.profileImageUrl.as("profileImageUrl"), - friend.member.introduction.as("introduction"), - ExpressionUtils.as( - select(Expressions.constant(true)) - .from(subFriend) - .where( - friend.member.id.eq( - subFriend.following.id), - friend.following.id.eq( - subFriend.member.id)), - "hasFollowedBack"))) - .from(friend) - .where(friend.following.id.eq(memberId), ltCursorId(cursorId)) - .limit(11) - .orderBy(friend.id.desc()) - .fetch(); - - boolean hasNext = false; - Long nextCursorId = null; - if (result.size() > 10) { - result = new ArrayList<>(result); - result.removeLast(); - hasNext = true; - nextCursorId = result.getLast().getFriendId(); - } - return FollowSlice.builder() - .followContents(result) - .pageSize(result.size()) - .cursorId(nextCursorId) - .hasNext(hasNext) - .build(); + return queryFactory + .select( + Projections.constructor( + Follower.class, + friend.id.as("friendId"), + friend.member.id.as("memberId"), + friend.member.nickname.as("name"), + friend.member.profileImageUrl.as("profileImageUrl"), + friend.member.introduction.as("introduction"), + ExpressionUtils.as( + select(Expressions.constant(true)) + .from(subFriend) + .where( + friend.member.id.eq(subFriend.following.id), + friend.following.id.eq( + subFriend.member.id)), + "hasFollowedBack"))) + .from(friend) + .where(friend.following.id.eq(memberId), ltCursorId(cursorId)) + .limit(11) + .orderBy(friend.id.desc()) + .fetch(); } @Override diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java index 9a5fa514..4d2540c2 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java @@ -69,6 +69,13 @@ public MemberEntity( this.lastViewedFollowingLogAt = lastViewedFollowingLogAt; } + public MemberEntity(Long id, String nickname, String profileImageUrl, String introduction) { + this.id = id; + this.nickname = nickname; + this.profileImageUrl = profileImageUrl; + this.introduction = introduction; + } + @PrePersist public void prePersist() { this.goal = 1000; diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/repository/MemberRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/repository/MemberRepository.java index 9a927b10..a4417e4f 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/repository/MemberRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/repository/MemberRepository.java @@ -3,12 +3,14 @@ import com.depromeet.friend.entity.QFriendEntity; import com.depromeet.member.domain.Member; import com.depromeet.member.domain.MemberGender; +import com.depromeet.member.domain.vo.MemberIdAndNickname; import com.depromeet.member.domain.vo.MemberSearchInfo; import com.depromeet.member.domain.vo.MemberSearchPage; import com.depromeet.member.entity.MemberEntity; import com.depromeet.member.entity.QMemberEntity; import com.depromeet.member.port.in.command.UpdateMemberCommand; import com.depromeet.member.port.out.persistence.MemberPersistencePort; +import com.querydsl.core.Tuple; import com.querydsl.core.types.ExpressionUtils; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; @@ -150,6 +152,21 @@ public Optional updateProfileImageUrl(Long memberId, String profileImage .map(memberEntity -> memberEntity.updateProfileImageUrl(profileImageUrl).toModel()); } + @Override + public Optional findIdAndNicknameById(Long memberId) { + Tuple result = + queryFactory + .select(member.id, member.nickname) + .from(member) + .where(member.id.eq(memberId)) + .fetchOne(); + if (result == null) { + return Optional.empty(); + } + return Optional.of( + new MemberIdAndNickname(result.get(0, Long.class), result.get(1, String.class))); + } + @Override public Optional updateGender(Long memberId, MemberGender gender) { return memberJpaRepository diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java index c24f32f7..2ab9cd5f 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java @@ -6,8 +6,10 @@ import static com.depromeet.memory.entity.QStrokeEntity.strokeEntity; import static com.depromeet.pool.entity.QPoolEntity.poolEntity; +import com.depromeet.member.domain.vo.MemberIdAndNickname; import com.depromeet.memory.domain.Memory; import com.depromeet.memory.domain.vo.MemoryAndDetailId; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; import com.depromeet.memory.entity.MemoryEntity; import com.depromeet.memory.entity.QMemoryEntity; import com.depromeet.memory.port.out.persistence.MemoryPersistencePort; @@ -216,6 +218,25 @@ public void deleteById(Long memoryId) { memoryJpaRepository.deleteById(memoryId); } + @Override + public Optional findIdAndNicknameById(Long memoryId) { + Tuple result = + queryFactory + .select(memory.id, memory.diary, memory.member.id, memory.member.nickname) + .from(memory) + .join(memory.member, memberEntity) + .where(memory.id.eq(memoryId)) + .fetchOne(); + if (result == null) { + return Optional.empty(); + } + MemberIdAndNickname member = + new MemberIdAndNickname(result.get(2, Long.class), result.get(3, String.class)); + return Optional.of( + new MemoryIdAndDiaryAndMember( + result.get(0, Long.class), result.get(1, String.class), member)); + } + private BooleanExpression loeRecordAt(LocalDate recordAt) { if (recordAt == null) { return null; diff --git a/module-infrastructure/persistence-database/src/test/java/com/depromeet/friend/repository/FriendRepositoryTest.java b/module-infrastructure/persistence-database/src/test/java/com/depromeet/friend/repository/FriendRepositoryTest.java index c027437e..acbfff5d 100644 --- a/module-infrastructure/persistence-database/src/test/java/com/depromeet/friend/repository/FriendRepositoryTest.java +++ b/module-infrastructure/persistence-database/src/test/java/com/depromeet/friend/repository/FriendRepositoryTest.java @@ -5,7 +5,6 @@ import com.depromeet.TestQueryDslConfig; import com.depromeet.fixture.domain.member.MemberFixture; import com.depromeet.friend.domain.Friend; -import com.depromeet.friend.domain.vo.FollowSlice; import com.depromeet.friend.domain.vo.Follower; import com.depromeet.friend.domain.vo.Following; import com.depromeet.member.domain.Member; @@ -121,14 +120,15 @@ private Long saveFriend() { expectedFollowing.stream().map(Following::getName).toList(); // when - FollowSlice result = + List result = friendRepository.findFollowingsByMemberIdAndCursorId(member.getId(), null); - List resultFollowingNames = - result.getFollowContents().stream().map(Following::getName).toList(); + List resultFollowingNames = result.stream().map(Following::getName).toList(); + if (resultFollowingNames.size() > 10) { + resultFollowingNames = new ArrayList<>(resultFollowingNames); + resultFollowingNames.removeLast(); + } // then - assertThat(result.getFollowContents()).isNotNull(); - assertThat(result.getFollowContents()).hasSize(10); assertThat(resultFollowingNames) .containsExactlyInAnyOrderElementsOf(expectedFollowingNames); } @@ -157,14 +157,15 @@ private Long saveFriend() { expectedFollowers.stream().map(Follower::getName).toList(); // when - FollowSlice result = + List result = friendRepository.findFollowersByMemberIdAndCursorId(member.getId(), null); - List resultFollowerNames = - result.getFollowContents().stream().map(Follower::getName).toList(); + List resultFollowerNames = result.stream().map(Follower::getName).toList(); + if (resultFollowerNames.size() > 10) { + resultFollowerNames = new ArrayList<>(resultFollowerNames); + resultFollowerNames.removeLast(); + } // then - assertThat(result.getFollowContents()).isNotNull(); - assertThat(result.getFollowContents()).hasSize(10); assertThat(resultFollowerNames).containsExactlyInAnyOrderElementsOf(expectedFollowerNames); } diff --git a/module-presentation/src/main/java/com/depromeet/blacklist/api/BlacklistApi.java b/module-presentation/src/main/java/com/depromeet/blacklist/api/BlacklistApi.java new file mode 100644 index 00000000..f22ed158 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/blacklist/api/BlacklistApi.java @@ -0,0 +1,28 @@ +package com.depromeet.blacklist.api; + +import com.depromeet.blacklist.dto.request.BlackMemberRequest; +import com.depromeet.blacklist.dto.response.BlackMemberResponse; +import com.depromeet.dto.response.ApiResponse; +import com.depromeet.member.annotation.LoginMember; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +@Tag(name = "차단(Blacklist)") +public interface BlacklistApi { + @Operation(summary = "사용자 차단") + ApiResponse blackMember( + @LoginMember Long memberId, @Valid @RequestBody BlackMemberRequest request); + + @Operation(summary = "사용자 차단 해제") + ApiResponse unblackMember( + @LoginMember Long memberId, @PathVariable("blackMemberId") Long blackMemberId); + + @Operation(summary = "사용자 차단 목록 조회") + ApiResponse getBlackMembers( + @LoginMember Long memberId, + @RequestParam(value = "cursorId", required = false) Long cursorId); +} diff --git a/module-presentation/src/main/java/com/depromeet/blacklist/api/BlacklistController.java b/module-presentation/src/main/java/com/depromeet/blacklist/api/BlacklistController.java new file mode 100644 index 00000000..b3c8eb01 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/blacklist/api/BlacklistController.java @@ -0,0 +1,51 @@ +package com.depromeet.blacklist.api; + +import com.depromeet.blacklist.dto.request.BlackMemberRequest; +import com.depromeet.blacklist.dto.response.BlackMemberResponse; +import com.depromeet.blacklist.facade.BlacklistFacade; +import com.depromeet.config.Logging; +import com.depromeet.dto.response.ApiResponse; +import com.depromeet.member.annotation.LoginMember; +import com.depromeet.type.blacklist.BlacklistSuccessType; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/member") +public class BlacklistController implements BlacklistApi { + private final BlacklistFacade blacklistFacade; + + @PostMapping("/black") + @Logging(item = "Blacklist", action = "POST") + public ApiResponse blackMember( + @LoginMember Long memberId, @Valid @RequestBody BlackMemberRequest request) { + blacklistFacade.blackMember(memberId, request); + return ApiResponse.success(BlacklistSuccessType.BLACK_MEMBER_SUCCESS); + } + + @DeleteMapping("/{blackMemberId}/black") + @Logging(item = "Blacklist", action = "DELETE") + public ApiResponse unblackMember( + @LoginMember Long memberId, @PathVariable("blackMemberId") Long blackMemberId) { + blacklistFacade.unblackMember(memberId, blackMemberId); + return ApiResponse.success(BlacklistSuccessType.UNBLACK_MEMBER_SUCCESS); + } + + @GetMapping("/black") + @Logging(item = "Blacklist", action = "GET") + public ApiResponse getBlackMembers( + @LoginMember Long memberId, + @RequestParam(value = "cursorId", required = false) Long cursorId) { + BlackMemberResponse response = blacklistFacade.getBlackMembers(memberId, cursorId); + return ApiResponse.success(BlacklistSuccessType.GET_BLACK_MEMBERS_SUCCESS, response); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/blacklist/dto/request/BlackMemberRequest.java b/module-presentation/src/main/java/com/depromeet/blacklist/dto/request/BlackMemberRequest.java new file mode 100644 index 00000000..68e7d9f3 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/blacklist/dto/request/BlackMemberRequest.java @@ -0,0 +1,14 @@ +package com.depromeet.blacklist.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +public record BlackMemberRequest( + @NotNull + @Positive + @Schema( + description = "차단 대상 사용자 ID", + example = "1", + requiredMode = Schema.RequiredMode.REQUIRED) + Long blackMemberId) {} diff --git a/module-presentation/src/main/java/com/depromeet/blacklist/dto/response/BlackMemberDetailResponse.java b/module-presentation/src/main/java/com/depromeet/blacklist/dto/response/BlackMemberDetailResponse.java new file mode 100644 index 00000000..b0da3638 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/blacklist/dto/response/BlackMemberDetailResponse.java @@ -0,0 +1,41 @@ +package com.depromeet.blacklist.dto.response; + +import com.depromeet.member.domain.Member; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record BlackMemberDetailResponse( + @Schema( + description = "차단한 유저 아이디", + example = "2", + requiredMode = Schema.RequiredMode.REQUIRED) + Long memberId, + @Schema( + description = "차단한 유저 닉네임", + example = "스탑제로", + requiredMode = Schema.RequiredMode.REQUIRED) + String nickname, + @Schema( + description = "차단한 유저 프로필 이미지", + example = "https://example.com/image.jpg OR 기본 이미지(1,2,3,4)", + requiredMode = Schema.RequiredMode.REQUIRED) + String profileImageUrl, + @Schema( + description = "차단한 유저 소개", + example = "안녕하세요", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String introduction) { + public static List of(List blackMembers, String domain) { + return blackMembers.stream() + .map( + member -> + new BlackMemberDetailResponse( + member.getId(), + member.getNickname(), + member.getProfileImageUrl(domain), + member.getIntroduction())) + .toList(); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/blacklist/dto/response/BlackMemberResponse.java b/module-presentation/src/main/java/com/depromeet/blacklist/dto/response/BlackMemberResponse.java new file mode 100644 index 00000000..cae6dc58 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/blacklist/dto/response/BlackMemberResponse.java @@ -0,0 +1,28 @@ +package com.depromeet.blacklist.dto.response; + +import com.depromeet.blacklist.domain.vo.BlacklistPage; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record BlackMemberResponse( + @Schema(description = "차단한 유저 리스트", requiredMode = Schema.RequiredMode.REQUIRED) + List blackMembers, + @Schema( + description = "다음 페이지가 존재하는지 여부", + example = "true", + requiredMode = Schema.RequiredMode.REQUIRED) + boolean hasNext, + @Schema( + description = "다음 페이지 검색 커서 ID(hasNext가 false일 시 null)", + example = "11", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + Long cursorId) { + public static BlackMemberResponse of(BlacklistPage page, String domain) { + return new BlackMemberResponse( + BlackMemberDetailResponse.of(page.blackMembers(), domain), + page.hasNext(), + page.cursorId()); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/blacklist/facade/BlacklistFacade.java b/module-presentation/src/main/java/com/depromeet/blacklist/facade/BlacklistFacade.java new file mode 100644 index 00000000..df74d671 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/blacklist/facade/BlacklistFacade.java @@ -0,0 +1,48 @@ +package com.depromeet.blacklist.facade; + +import com.depromeet.blacklist.domain.vo.BlacklistPage; +import com.depromeet.blacklist.dto.request.BlackMemberRequest; +import com.depromeet.blacklist.dto.response.BlackMemberResponse; +import com.depromeet.blacklist.port.in.usecase.BlacklistCommandUseCase; +import com.depromeet.blacklist.port.in.usecase.BlacklistQueryUseCase; +import com.depromeet.exception.BadRequestException; +import com.depromeet.type.blacklist.BlacklistErrorType; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class BlacklistFacade { + private final BlacklistQueryUseCase blacklistQueryUseCase; + private final BlacklistCommandUseCase blacklistCommandUseCase; + + @Value("${cloud-front.domain}") + private String domain; + + public void blackMember(Long memberId, BlackMemberRequest request) { + Long blackMemberId = request.blackMemberId(); + if (memberId.equals(blackMemberId)) { + throw new BadRequestException(BlacklistErrorType.CANNOT_BLACK_MYSELF); + } + + boolean isAlreadyBlacked = blacklistQueryUseCase.checkBlackMember(memberId, blackMemberId); + if (isAlreadyBlacked) { + throw new BadRequestException(BlacklistErrorType.ALREADY_BLACKED); + } + + blacklistCommandUseCase.blackMember(memberId, blackMemberId); + } + + public void unblackMember(Long memberId, Long blackMemberId) { + blacklistCommandUseCase.unblackMember(memberId, blackMemberId); + } + + @Transactional(readOnly = true) + public BlackMemberResponse getBlackMembers(Long memberId, Long cursorId) { + BlacklistPage page = blacklistQueryUseCase.getBlackMembers(memberId, cursorId); + return BlackMemberResponse.of(page, domain); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/friend/dto/response/FollowSliceResponse.java b/module-presentation/src/main/java/com/depromeet/friend/dto/response/FollowSliceResponse.java index 32384ef0..0a762cb9 100644 --- a/module-presentation/src/main/java/com/depromeet/friend/dto/response/FollowSliceResponse.java +++ b/module-presentation/src/main/java/com/depromeet/friend/dto/response/FollowSliceResponse.java @@ -28,32 +28,36 @@ public record FollowSliceResponse( public FollowSliceResponse {} public static FollowSliceResponse toFollowingSliceResponse( - FollowSlice followingSlice, String profileImageDomain) { + FollowSlice followingSlice, + List filteredFollowings, + String profileImageDomain) { List followingResponses = - getFollowingResponses(followingSlice, profileImageDomain); + getFollowingResponses(filteredFollowings, profileImageDomain); return FollowSliceResponse.builder() .contents(followingResponses) - .pageSize(followingSlice.getPageSize()) + .pageSize(followingResponses.size()) .cursorId(followingSlice.getCursorId()) .hasNext(followingSlice.isHasNext()) .build(); } public static FollowSliceResponse toFollowerSliceResponses( - FollowSlice followingSlice, String profileImageOrigin) { - List followingResponses = - getFollowerResponses(followingSlice, profileImageOrigin); + FollowSlice followingSlice, + List filteredFollowers, + String profileImageOrigin) { + List followerResponses = + getFollowerResponses(filteredFollowers, profileImageOrigin); return FollowSliceResponse.builder() - .contents(followingResponses) - .pageSize(followingSlice.getPageSize()) + .contents(followerResponses) + .pageSize(followerResponses.size()) .cursorId(followingSlice.getCursorId()) .hasNext(followingSlice.isHasNext()) .build(); } private static List getFollowingResponses( - FollowSlice followingSlice, String profileImageOrigin) { - return followingSlice.getFollowContents().stream() + List followings, String profileImageOrigin) { + return followings.stream() .map( following -> FollowingResponse.builder() @@ -67,8 +71,8 @@ private static List getFollowingResponses( } private static List getFollowerResponses( - FollowSlice followingSlice, String profileImageOrigin) { - return followingSlice.getFollowContents().stream() + List followers, String profileImageOrigin) { + return followers.stream() .map( follower -> FollowerResponse.builder() diff --git a/module-presentation/src/main/java/com/depromeet/friend/facade/FollowFacade.java b/module-presentation/src/main/java/com/depromeet/friend/facade/FollowFacade.java index 97f15e0d..46326abd 100644 --- a/module-presentation/src/main/java/com/depromeet/friend/facade/FollowFacade.java +++ b/module-presentation/src/main/java/com/depromeet/friend/facade/FollowFacade.java @@ -1,5 +1,6 @@ package com.depromeet.friend.facade; +import com.depromeet.blacklist.port.in.usecase.BlacklistQueryUseCase; import com.depromeet.friend.domain.vo.FollowCheck; import com.depromeet.friend.domain.vo.FollowSlice; import com.depromeet.friend.domain.vo.Follower; @@ -11,6 +12,7 @@ import com.depromeet.member.port.in.usecase.MemberUseCase; import com.depromeet.notification.event.FollowLogEvent; import java.util.List; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; @@ -23,6 +25,7 @@ public class FollowFacade { private final FollowUseCase followUseCase; private final MemberUseCase memberUseCase; + private final BlacklistQueryUseCase blacklistQueryUseCase; private final ApplicationEventPublisher eventPublisher; @Value("${cloud-front.domain}") @@ -40,13 +43,28 @@ public boolean addOrDeleteFollow(Long memberId, FollowRequest followRequest) { public FollowSliceResponse findFollowingList(Long memberId, Long cursorId) { FollowSlice followingSlice = followUseCase.getFollowingByMemberIdAndCursorId(memberId, cursorId); - return FollowSliceResponse.toFollowingSliceResponse(followingSlice, profileImageOrigin); + Set blackMemberIds = blacklistQueryUseCase.getBlackMemberIds(memberId); + + List filteredFollowings = + followingSlice.getFollowContents().stream() + .filter(following -> !blackMemberIds.contains(following.getMemberId())) + .toList(); + + return FollowSliceResponse.toFollowingSliceResponse( + followingSlice, filteredFollowings, profileImageOrigin); } public FollowSliceResponse findFollowerList(Long memberId, Long cursorId) { FollowSlice followerSlice = followUseCase.getFollowerByMemberIdAndCursorId(memberId, cursorId); - return FollowSliceResponse.toFollowerSliceResponses(followerSlice, profileImageOrigin); + Set blackMemberIds = blacklistQueryUseCase.getBlackMemberIds(memberId); + List filteredFollowers = + followerSlice.getFollowContents().stream() + .filter(following -> !blackMemberIds.contains(following.getMemberId())) + .toList(); + + return FollowSliceResponse.toFollowerSliceResponses( + followerSlice, filteredFollowers, profileImageOrigin); } public FollowingSummaryResponse findFollowingSummary(Long memberId) { diff --git a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java index 5738698a..51865d5b 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java +++ b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java @@ -79,6 +79,7 @@ public ApiResponse getCalendar( } @DeleteMapping("/{memoryId}") + @Logging(item = "Memory", action = "DELETE") public ApiResponse delete( @LoginMember Long memberId, @PathVariable("memoryId") Long memoryId) { memoryFacade.deleteById(memberId, memoryId); diff --git a/module-presentation/src/main/java/com/depromeet/report/api/ReportApi.java b/module-presentation/src/main/java/com/depromeet/report/api/ReportApi.java new file mode 100644 index 00000000..4f92cf72 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/report/api/ReportApi.java @@ -0,0 +1,14 @@ +package com.depromeet.report.api; + +import com.depromeet.dto.response.ApiResponse; +import com.depromeet.member.annotation.LoginMember; +import com.depromeet.report.dto.request.ReportRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RequestBody; + +@Tag(name = "기록 신고(Report)") +public interface ReportApi { + @Operation(description = "기록 신고 제출") + ApiResponse create(@LoginMember Long memberId, @RequestBody ReportRequest request); +} diff --git a/module-presentation/src/main/java/com/depromeet/report/api/ReportController.java b/module-presentation/src/main/java/com/depromeet/report/api/ReportController.java new file mode 100644 index 00000000..69f4adb2 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/report/api/ReportController.java @@ -0,0 +1,29 @@ +package com.depromeet.report.api; + +import com.depromeet.config.Logging; +import com.depromeet.dto.response.ApiResponse; +import com.depromeet.member.annotation.LoginMember; +import com.depromeet.report.dto.request.ReportRequest; +import com.depromeet.report.facade.ReportFacade; +import com.depromeet.type.report.ReportSuccessType; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/report") +public class ReportController implements ReportApi { + private final ReportFacade reportFacade; + + @PostMapping + @Logging(item = "Report", action = "POST") + public ApiResponse create( + @LoginMember Long memberId, @Valid @RequestBody ReportRequest request) { + reportFacade.save(memberId, request); + return ApiResponse.success(ReportSuccessType.POST_RESULT_SUCCESS); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/report/dto/request/ReportRequest.java b/module-presentation/src/main/java/com/depromeet/report/dto/request/ReportRequest.java new file mode 100644 index 00000000..a24fd93a --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/report/dto/request/ReportRequest.java @@ -0,0 +1,19 @@ +package com.depromeet.report.dto.request; + +import com.depromeet.report.domain.ReportReasonCode; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +public record ReportRequest( + @NotNull + @Schema( + description = "신고할 기록 id", + example = "1", + requiredMode = Schema.RequiredMode.REQUIRED) + Long memoryId, + @NotNull + @Schema( + description = "신고 사유 code", + example = "REPORT_REASON_1", + requiredMode = Schema.RequiredMode.REQUIRED) + ReportReasonCode reasonCode) {} diff --git a/module-presentation/src/main/java/com/depromeet/report/facade/ReportFacade.java b/module-presentation/src/main/java/com/depromeet/report/facade/ReportFacade.java new file mode 100644 index 00000000..30077884 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/report/facade/ReportFacade.java @@ -0,0 +1,41 @@ +package com.depromeet.report.facade; + +import com.depromeet.exception.BadRequestException; +import com.depromeet.image.domain.Image; +import com.depromeet.image.port.in.ImageGetUseCase; +import com.depromeet.member.domain.vo.MemberIdAndNickname; +import com.depromeet.member.port.in.usecase.MemberUseCase; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; +import com.depromeet.memory.port.in.usecase.GetMemoryUseCase; +import com.depromeet.report.dto.request.ReportRequest; +import com.depromeet.report.mapper.ReportMapper; +import com.depromeet.report.port.in.command.CreateReportCommand; +import com.depromeet.report.port.in.usecase.CreateReportUseCase; +import com.depromeet.type.report.ReportErrorType; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ReportFacade { + private final MemberUseCase memberUseCase; + private final ImageGetUseCase imageGetUseCase; + private final GetMemoryUseCase getMemoryUseCase; + private final CreateReportUseCase createReportUseCase; + + @Transactional + public void save(Long memberId, ReportRequest request) { + MemberIdAndNickname member = memberUseCase.findIdAndNicknameById(memberId); + MemoryIdAndDiaryAndMember reportMemory = + getMemoryUseCase.findIdAndNicknameById(request.memoryId()); + if (memberId.equals(reportMemory.member().id())) { + throw new BadRequestException(ReportErrorType.CANNOT_REPORT_OWN_MEMORY); + } + List images = imageGetUseCase.findImagesByMemoryId(reportMemory.id()); + CreateReportCommand command = + ReportMapper.toCommand(member, reportMemory, images, request.reasonCode()); + createReportUseCase.save(command); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/report/mapper/ReportMapper.java b/module-presentation/src/main/java/com/depromeet/report/mapper/ReportMapper.java new file mode 100644 index 00000000..f9938910 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/report/mapper/ReportMapper.java @@ -0,0 +1,18 @@ +package com.depromeet.report.mapper; + +import com.depromeet.image.domain.Image; +import com.depromeet.member.domain.vo.MemberIdAndNickname; +import com.depromeet.memory.domain.vo.MemoryIdAndDiaryAndMember; +import com.depromeet.report.domain.ReportReasonCode; +import com.depromeet.report.port.in.command.CreateReportCommand; +import java.util.List; + +public class ReportMapper { + public static CreateReportCommand toCommand( + MemberIdAndNickname member, + MemoryIdAndDiaryAndMember reportMemory, + List images, + ReportReasonCode reasonCode) { + return new CreateReportCommand(member, reportMemory, images, reasonCode); + } +}