From 1000bed3688e7afec11069fb7e716ab142d78b47 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Tue, 12 Sep 2023 18:03:11 +0900 Subject: [PATCH 01/25] =?UTF-8?q?feat:=20=EC=A0=84=EC=B2=B4=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/application/AdminQueryService.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java new file mode 100644 index 00000000..6b35fe8f --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -0,0 +1,46 @@ +package com.mapbefine.mapbefine.admin.application; + +import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.dto.response.MemberDetailResponse; +import com.mapbefine.mapbefine.member.exception.MemberErrorCode; +import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; +import com.mapbefine.mapbefine.permission.exception.PermissionErrorCode; +import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class AdminQueryService { + + private final MemberRepository memberRepository; + + public AdminQueryService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + public List findAllMemberDetails(AuthMember authMember) { + Member admin = findMemberById(authMember.getMemberId()); + validateAdminPermission(admin); + + List members = memberRepository.findAll(); + + return members.stream() + .map(MemberDetailResponse::from) + .toList(); + } + + private Member findMemberById(Long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new MemberNotFoundException(MemberErrorCode.MEMBER_NOT_FOUND, id)); + } + + private void validateAdminPermission(Member member) { + if (member.isAdmin()) { + return; + } + + throw new PermissionForbiddenException(PermissionErrorCode.PERMISSION_FORBIDDEN_BY_NOT_ADMIN); + } +} From 0a7f1cb7c1c599dff1470f10a117b399dfd470ec Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Tue, 12 Sep 2023 18:03:26 +0900 Subject: [PATCH 02/25] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C(=ED=83=88=ED=87=B4)=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 41 +++++++++++++++++++ .../exception/PermissionErrorCode.java | 3 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java new file mode 100644 index 00000000..e8a68d07 --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -0,0 +1,41 @@ +package com.mapbefine.mapbefine.admin.application; + +import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.exception.MemberErrorCode; +import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; +import com.mapbefine.mapbefine.permission.exception.PermissionErrorCode; +import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; +import org.springframework.stereotype.Service; + +@Service +public class AdminCommandService { + + private final MemberRepository memberRepository; + + public AdminCommandService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + // TODO: 2023/09/12 블랙리스트..? + public void deleteMember(AuthMember authMember, Long memberId) { + Member admin = findMemberById(authMember.getMemberId()); + validateAdminPermission(admin); + + memberRepository.deleteById(memberId); + } + + private Member findMemberById(Long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new MemberNotFoundException(MemberErrorCode.MEMBER_NOT_FOUND, id)); + } + + private void validateAdminPermission(Member member) { + if (member.isAdmin()) { + return; + } + + throw new PermissionForbiddenException(PermissionErrorCode.PERMISSION_FORBIDDEN_BY_NOT_ADMIN); + } +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/permission/exception/PermissionErrorCode.java b/backend/src/main/java/com/mapbefine/mapbefine/permission/exception/PermissionErrorCode.java index 5a0f6aa8..c3f3f699 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/permission/exception/PermissionErrorCode.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/permission/exception/PermissionErrorCode.java @@ -9,7 +9,8 @@ public enum PermissionErrorCode { ILLEGAL_PERMISSION_ID("07001", "유효하지 않은 권한 정보입니다."), FORBIDDEN_ADD_PERMISSION_GUEST("07300", "로그인하지 않은 사용자는 권한을 줄 수 없습니다."), FORBIDDEN_ADD_PERMISSION("07301", "지도를 생성한 사용자가 아니면 권한을 줄 수 없습니다."), - PERMISSION_NOT_FOUND("07400", "존재하지 않는 권한 정보입니다.") + PERMISSION_NOT_FOUND("07400", "존재하지 않는 권한 정보입니다."), + PERMISSION_FORBIDDEN_BY_NOT_ADMIN("07401", "어드민 계정만 접근 가능합니다."), ; private final String code; From 540a6447a3ffb19b584a661933f1632d156a0f8a Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Tue, 12 Sep 2023 19:21:50 +0900 Subject: [PATCH 03/25] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C(=ED=83=88=ED=87=B4)=EC=8B=9C=20Pin/Topic=20Soft-delet?= =?UTF-8?q?ing=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mapbefine/mapbefine/member/domain/Member.java | 11 ++++++----- .../java/com/mapbefine/mapbefine/pin/domain/Pin.java | 2 ++ .../com/mapbefine/mapbefine/topic/domain/Topic.java | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java index 71de6abf..4ef884a4 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java @@ -9,6 +9,7 @@ import com.mapbefine.mapbefine.permission.domain.Permission; import com.mapbefine.mapbefine.pin.domain.Pin; import com.mapbefine.mapbefine.topic.domain.Topic; +import jakarta.persistence.CascadeType; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -39,19 +40,19 @@ public class Member extends BaseTimeEntity { @Embedded private OauthId oauthId; - @OneToMany(mappedBy = "creator") + @OneToMany(mappedBy = "creator", cascade = CascadeType.REMOVE) private List createdTopics = new ArrayList<>(); - @OneToMany(mappedBy = "creator") + @OneToMany(mappedBy = "creator", cascade = CascadeType.REMOVE) private List createdPins = new ArrayList<>(); - @OneToMany(mappedBy = "member") + @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) private List topicsWithPermissions = new ArrayList<>(); - @OneToMany(mappedBy = "member") + @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) private List bookmarks = new ArrayList<>(); - @OneToMany(mappedBy = "member") + @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) private List atlantes = new ArrayList<>(); private Member(MemberInfo memberInfo, OauthId oauthId) { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java index 194bcaf1..e36263cf 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java @@ -22,8 +22,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.SQLDelete; @Entity +@SQLDelete(sql = "UPDATE pin SET is_deleted = true WHERE id = ?") @NoArgsConstructor(access = PROTECTED) @Getter public class Pin extends BaseTimeEntity { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java index a41c807e..f31a6f28 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java @@ -22,8 +22,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.SQLDelete; @Entity +@SQLDelete(sql = "UPDATE topic SET is_deleted = true WHERE id = ?") @NoArgsConstructor(access = PROTECTED) @Getter public class Topic extends BaseTimeEntity { From 1dd3c73de6b103b705b95d66423e318732d8a263 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Tue, 12 Sep 2023 19:23:15 +0900 Subject: [PATCH 04/25] =?UTF-8?q?refactor:=20Admin=20DTO=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/application/AdminQueryService.java | 5 ++-- .../admin/dto/AdminMemberResponse.java | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberResponse.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java index 6b35fe8f..8fea8f92 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -1,5 +1,6 @@ package com.mapbefine.mapbefine.admin.application; +import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse; import com.mapbefine.mapbefine.auth.domain.AuthMember; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; @@ -20,14 +21,14 @@ public AdminQueryService(MemberRepository memberRepository) { this.memberRepository = memberRepository; } - public List findAllMemberDetails(AuthMember authMember) { + public List findAllMemberDetails(AuthMember authMember) { Member admin = findMemberById(authMember.getMemberId()); validateAdminPermission(admin); List members = memberRepository.findAll(); return members.stream() - .map(MemberDetailResponse::from) + .map(AdminMemberResponse::from) .toList(); } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberResponse.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberResponse.java new file mode 100644 index 00000000..f041f8f1 --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberResponse.java @@ -0,0 +1,27 @@ +package com.mapbefine.mapbefine.admin.dto; + +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberInfo; +import java.time.LocalDateTime; + +public record AdminMemberResponse( + Long id, + String nickName, + String email, + String imageUrl, + LocalDateTime updatedAt +) { + + public static AdminMemberResponse from(Member member) { + MemberInfo memberInfo = member.getMemberInfo(); + + return new AdminMemberResponse( + member.getId(), + memberInfo.getNickName(), + memberInfo.getEmail(), + memberInfo.getImageUrl(), + member.getUpdatedAt() + ); + } + +} From 1c50017e94c310e8c5fbda2364d2c65ac9273d14 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Tue, 12 Sep 2023 20:14:59 +0900 Subject: [PATCH 05/25] =?UTF-8?q?feat:=20Member=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=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 --- .../admin/application/AdminQueryService.java | 16 ++++++- .../admin/dto/AdminMemberDetailResponse.java | 48 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberDetailResponse.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java index 8fea8f92..6d96ebbe 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -1,14 +1,16 @@ package com.mapbefine.mapbefine.admin.application; +import com.mapbefine.mapbefine.admin.dto.AdminMemberDetailResponse; import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse; import com.mapbefine.mapbefine.auth.domain.AuthMember; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; -import com.mapbefine.mapbefine.member.dto.response.MemberDetailResponse; import com.mapbefine.mapbefine.member.exception.MemberErrorCode; import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; import com.mapbefine.mapbefine.permission.exception.PermissionErrorCode; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; +import com.mapbefine.mapbefine.pin.domain.Pin; +import com.mapbefine.mapbefine.topic.domain.Topic; import java.util.List; import org.springframework.stereotype.Service; @@ -44,4 +46,16 @@ private void validateAdminPermission(Member member) { throw new PermissionForbiddenException(PermissionErrorCode.PERMISSION_FORBIDDEN_BY_NOT_ADMIN); } + + public AdminMemberDetailResponse findMemberDetail(AuthMember authMember, Long memberId) { + Member member = findMemberById(authMember.getMemberId()); + + validateAdminPermission(member); + + Member findMember = findMemberById(memberId); + List topics = findMember.getCreatedTopics(); + List pins = findMember.getCreatedPins(); + + return AdminMemberDetailResponse.of(member, topics, pins); + } } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberDetailResponse.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberDetailResponse.java new file mode 100644 index 00000000..fbd5e04d --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/dto/AdminMemberDetailResponse.java @@ -0,0 +1,48 @@ +package com.mapbefine.mapbefine.admin.dto; + +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberInfo; +import com.mapbefine.mapbefine.pin.domain.Pin; +import com.mapbefine.mapbefine.pin.dto.response.PinResponse; +import com.mapbefine.mapbefine.topic.domain.Topic; +import com.mapbefine.mapbefine.topic.dto.response.TopicResponse; +import java.time.LocalDateTime; +import java.util.List; + +public record AdminMemberDetailResponse( + Long id, + String nickName, + String email, + String imageUrl, + List topics, + List pins, + LocalDateTime updatedAt +) { + + // TODO: 2023/09/12 topics, pins 모두 member를 통해 얻어올 수 있다. Service에서 꺼내서 넘겨줄 것인가 ? 아니면 DTO에서 꺼내올 것인가 ? + public static AdminMemberDetailResponse of( + Member member, + List topics, + List pins + ) { + MemberInfo memberInfo = member.getMemberInfo(); + List topicResponses = topics.stream() + .map(TopicResponse::fromGuestQuery) + .toList(); + List pinResponses = pins.stream() + .map(PinResponse::from) + .toList(); + + return new AdminMemberDetailResponse( + member.getId(), + memberInfo.getNickName(), + memberInfo.getEmail(), + memberInfo.getImageUrl(), + topicResponses, + pinResponses, + member.getUpdatedAt() + ); + } + + +} From 309b82751c9c30f1b00098fdaef9ef83c5135a4b Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Tue, 12 Sep 2023 21:56:52 +0900 Subject: [PATCH 06/25] =?UTF-8?q?feat:=20Topic=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index e8a68d07..40cd2a78 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -1,26 +1,42 @@ package com.mapbefine.mapbefine.admin.application; +import static com.mapbefine.mapbefine.permission.exception.PermissionErrorCode.PERMISSION_FORBIDDEN_BY_NOT_ADMIN; +import static com.mapbefine.mapbefine.topic.exception.TopicErrorCode.TOPIC_NOT_FOUND; + import com.mapbefine.mapbefine.auth.domain.AuthMember; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; import com.mapbefine.mapbefine.member.exception.MemberErrorCode; import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; -import com.mapbefine.mapbefine.permission.exception.PermissionErrorCode; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; +import com.mapbefine.mapbefine.pin.domain.PinImageRepository; +import com.mapbefine.mapbefine.pin.domain.PinRepository; +import com.mapbefine.mapbefine.topic.domain.Topic; +import com.mapbefine.mapbefine.topic.domain.TopicInfo; +import com.mapbefine.mapbefine.topic.domain.TopicRepository; +import com.mapbefine.mapbefine.topic.exception.TopicException; +import java.util.List; import org.springframework.stereotype.Service; @Service public class AdminCommandService { private final MemberRepository memberRepository; + private final TopicRepository topicRepository; - public AdminCommandService(MemberRepository memberRepository) { + public AdminCommandService( + MemberRepository memberRepository, + TopicRepository topicRepository + ) { this.memberRepository = memberRepository; + this.topicRepository = topicRepository; + } // TODO: 2023/09/12 블랙리스트..? public void deleteMember(AuthMember authMember, Long memberId) { Member admin = findMemberById(authMember.getMemberId()); + validateAdminPermission(admin); memberRepository.deleteById(memberId); @@ -36,6 +52,31 @@ private void validateAdminPermission(Member member) { return; } - throw new PermissionForbiddenException(PermissionErrorCode.PERMISSION_FORBIDDEN_BY_NOT_ADMIN); + throw new PermissionForbiddenException(PERMISSION_FORBIDDEN_BY_NOT_ADMIN); + } + + public void deleteTopic(AuthMember authMember, Long topicId) { + Member member = findMemberById(authMember.getMemberId()); + + validateAdminPermission(member); + + topicRepository.deleteById(topicId); } + + public void deleteTopicImage(AuthMember authMember, Long topicId) { + Member member = findMemberById(authMember.getMemberId()); + + validateAdminPermission(member); + + Topic topic = findTopicById(topicId); + TopicInfo topicInfo = topic.getTopicInfo(); + + topic.updateTopicInfo(topicInfo.getName(), topicInfo.getDescription(), ""); + } + + private Topic findTopicById(Long topicId) { + return topicRepository.findById(topicId) + .orElseThrow(() -> new TopicException.TopicNotFoundException(TOPIC_NOT_FOUND, List.of(topicId))); + } + } From 1535adab59b01d8eb6e5eebfbe4944a0ab90d913 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Tue, 12 Sep 2023 21:57:44 +0900 Subject: [PATCH 07/25] =?UTF-8?q?feat:=20Pin=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 25 +++++++++++++++++-- .../mapbefine/pin/domain/PinImage.java | 2 ++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index 40cd2a78..1336fb9b 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -23,14 +23,19 @@ public class AdminCommandService { private final MemberRepository memberRepository; private final TopicRepository topicRepository; + private final PinRepository pinRepository; + private final PinImageRepository pinImageRepository; public AdminCommandService( MemberRepository memberRepository, - TopicRepository topicRepository + TopicRepository topicRepository, + PinRepository pinRepository, + PinImageRepository pinImageRepository ) { this.memberRepository = memberRepository; this.topicRepository = topicRepository; - + this.pinRepository = pinRepository; + this.pinImageRepository = pinImageRepository; } // TODO: 2023/09/12 블랙리스트..? @@ -79,4 +84,20 @@ private Topic findTopicById(Long topicId) { .orElseThrow(() -> new TopicException.TopicNotFoundException(TOPIC_NOT_FOUND, List.of(topicId))); } + public void deletePin(AuthMember authMember, Long pinId) { + Member member = findMemberById(authMember.getMemberId()); + + validateAdminPermission(member); + + pinRepository.deleteById(pinId); + } + + public void deletePinImage(AuthMember authMember, Long pinImageId) { + Member member = findMemberById(authMember.getMemberId()); + + validateAdminPermission(member); + + pinImageRepository.deleteById(pinImageId); + } + } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java index 4da68c7d..63acb9d8 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java @@ -14,8 +14,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.SQLDelete; @Entity +@SQLDelete(sql = "UPDATE pin_image SET is_deleted = true WHERE id = ?") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class PinImage extends BaseTimeEntity { From d688eee865397e95212075f595415f8ee39fb32f Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 10:13:44 +0900 Subject: [PATCH 08/25] =?UTF-8?q?feat:=20Admin=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/presentation/AdminController.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java new file mode 100644 index 00000000..cccc3f2f --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java @@ -0,0 +1,79 @@ +package com.mapbefine.mapbefine.admin.presentation; + +import com.mapbefine.mapbefine.admin.application.AdminCommandService; +import com.mapbefine.mapbefine.admin.application.AdminQueryService; +import com.mapbefine.mapbefine.admin.dto.AdminMemberDetailResponse; +import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse; +import com.mapbefine.mapbefine.auth.domain.AuthMember; +import java.util.List; +import org.springframework.http.ResponseEntity; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +// TODO: 2023/09/12 컨트롤러 자체에서 검증할 수 있는 방법은 없을까 ? +@RestController +@RequestMapping("/admin") +public class AdminController { + + private final AdminQueryService adminQueryService; + private final AdminCommandService adminCommandService; + + + public AdminController(AdminQueryService adminQueryService, AdminCommandService adminCommandService) { + this.adminQueryService = adminQueryService; + this.adminCommandService = adminCommandService; + } + + @GetMapping("/members") + public ResponseEntity> findAllMembers(AuthMember authMember) { + List responses = adminQueryService.findAllMemberDetails(authMember); + + return ResponseEntity.ok(responses); + } + + @DeleteMapping("/members/{memberId}") + public ResponseEntity deleteMember(AuthMember authMember, @PathVariable Long memberId) { + adminCommandService.deleteMember(authMember, memberId); + + return ResponseEntity.noContent().build(); + } + + @GetMapping("/members/{memberId}") + public ResponseEntity findMember(AuthMember authMember, @PathVariable Long memberId) { + AdminMemberDetailResponse response = adminQueryService.findMemberDetail(authMember, memberId); + + return ResponseEntity.ok(response); + } + + @DeleteMapping("/topics/{topicId}") + public ResponseEntity deleteTopic(AuthMember authMember, @PathVariable Long topicId) { + adminCommandService.deleteTopic(authMember, topicId); + + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/topics/{topicId}/images") + public ResponseEntity deleteTopicImage(AuthMember authMember, @PathVariable Long topicId) { + adminCommandService.deleteTopicImage(authMember, topicId); + + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/pins/{pinId}") + public ResponseEntity deletePin(AuthMember authMember, @PathVariable Long pinId) { + adminCommandService.deletePin(authMember, pinId); + + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/pins/images/{imageId}") + public ResponseEntity deletePinImage(AuthMember authMember, @PathVariable Long imageId) { + adminCommandService.deletePinImage(authMember, imageId); + + return ResponseEntity.noContent().build(); + } + +} From 06ef99b88d0ab6e8d9e8bf640250bc8a4f7c7d37 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 13:29:34 +0900 Subject: [PATCH 09/25] =?UTF-8?q?refactor:=20Member=20=EC=83=81=ED=83=9C(?= =?UTF-8?q?=EC=B0=A8=EB=8B=A8,=20=ED=83=88=ED=87=B4=20=EB=93=B1)=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapbefine/member/domain/Member.java | 23 +++++++++++-------- .../mapbefine/member/domain/MemberInfo.java | 21 ++++++++++++++--- .../mapbefine/member/domain/Status.java | 15 ++++++++++++ .../member/exception/MemberErrorCode.java | 1 + .../member/exception/MemberException.java | 7 ++++++ .../oauth/application/OauthService.java | 12 ++++++++++ .../mapbefine/member/MemberFixture.java | 3 +++ .../member/domain/MemberInfoTest.java | 14 +++++++---- .../mapbefine/member/domain/MemberTest.java | 5 +++- 9 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/member/domain/Status.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java index 4ef884a4..fc0a2bb2 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java @@ -9,7 +9,6 @@ import com.mapbefine.mapbefine.permission.domain.Permission; import com.mapbefine.mapbefine.pin.domain.Pin; import com.mapbefine.mapbefine.topic.domain.Topic; -import jakarta.persistence.CascadeType; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -40,19 +39,19 @@ public class Member extends BaseTimeEntity { @Embedded private OauthId oauthId; - @OneToMany(mappedBy = "creator", cascade = CascadeType.REMOVE) + @OneToMany(mappedBy = "creator") private List createdTopics = new ArrayList<>(); - @OneToMany(mappedBy = "creator", cascade = CascadeType.REMOVE) + @OneToMany(mappedBy = "creator") private List createdPins = new ArrayList<>(); - @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) + @OneToMany(mappedBy = "member") private List topicsWithPermissions = new ArrayList<>(); - @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) + @OneToMany(mappedBy = "member") private List bookmarks = new ArrayList<>(); - @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) + @OneToMany(mappedBy = "member") private List atlantes = new ArrayList<>(); private Member(MemberInfo memberInfo, OauthId oauthId) { @@ -65,13 +64,15 @@ public static Member of( String email, String imageUrl, Role role, + Status status, OauthId oauthId ) { MemberInfo memberInfo = MemberInfo.of( nickName, email, imageUrl, - role + role, + status ); return new Member(memberInfo, oauthId); @@ -86,7 +87,7 @@ public static Member ofRandomNickname( ) { String nickName = createNickname(nickname); - return Member.of(nickName, email, imageUrl, role, oauthId); + return Member.of(nickName, email, imageUrl, role, Status.NORMAL, oauthId); } private static String createNickname(String nickname) { @@ -112,7 +113,8 @@ public void update( nickName, email, imageUrl, - memberInfo.getRole() + memberInfo.getRole(), + memberInfo.getStatus() ); } @@ -150,4 +152,7 @@ public List getTopicsWithPermissions() { .toList(); } + public boolean isNormalStatus() { + return memberInfo.getStatus() == Status.NORMAL; + } } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java index c15054c3..08276157 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java @@ -38,33 +38,42 @@ public class MemberInfo { @Column(nullable = false) private Role role; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Status status; + private MemberInfo( String nickName, String email, Image imageUrl, - Role role + Role role, + Status status ) { this.nickName = nickName; this.email = email; this.imageUrl = imageUrl; this.role = role; + this.status = status; } public static MemberInfo of( String nickName, String email, String imageUrl, - Role role + Role role, + Status status ) { validateNickName(nickName); validateEmail(email); validateRole(role); + validateStatus(status); return new MemberInfo( nickName, email, Image.from(imageUrl), - role + role, + status ); } @@ -93,6 +102,12 @@ private static void validateRole(Role role) { } } + private static void validateStatus(Status status) { + if (Objects.isNull(status)) { + throw new IllegalArgumentException("validateStatus; member status is null;"); + } + } + public String getImageUrl() { return imageUrl.getImageUrl(); } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Status.java b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Status.java new file mode 100644 index 00000000..bf594c09 --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Status.java @@ -0,0 +1,15 @@ +package com.mapbefine.mapbefine.member.domain; + +public enum Status { + NORMAL("STATUS_NORMAL", "정상 사용자"), + DELETE("STATAUS_DELETE", "탈퇴한 사용자"), + BLOCKED("STATUS_BLOCKED", "차단된 사용자"); + + private final String key; + private final String title; + + Status(String key, String title) { + this.key = key; + this.title = title; + } +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberErrorCode.java b/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberErrorCode.java index c02d2ab1..b0c5d034 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberErrorCode.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberErrorCode.java @@ -9,6 +9,7 @@ public enum MemberErrorCode { ILLEGAL_NICKNAME_LENGTH("05001", "닉네임 길이는 최소 1 자에서 20자여야 합니다."), ILLEGAL_EMAIL_NULL("05002", "이메일은 필수로 입력해야합니다."), ILLEGAL_EMAIL_PATTERN("05003", "올바르지 않은 이메일 형식입니다."), + FORBIDDEN_MEMBER_STATUS("05100", "탈퇴 혹은 차단된 회원입니다."), MEMBER_NOT_FOUND("05400", "존재하지 않는 회원입니다."), ; diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberException.java b/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberException.java index ec98e9d0..89a68948 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberException.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/exception/MemberException.java @@ -2,6 +2,7 @@ import com.mapbefine.mapbefine.common.exception.BadRequestException; import com.mapbefine.mapbefine.common.exception.ErrorCode; +import com.mapbefine.mapbefine.common.exception.ForbiddenException; import com.mapbefine.mapbefine.common.exception.NotFoundException; public class MemberException { @@ -18,5 +19,11 @@ public MemberNotFoundException(MemberErrorCode errorCode, Long id) { } } + public static class MemberForbiddenException extends ForbiddenException { + public MemberForbiddenException(MemberErrorCode errorCode, Long id) { + super(new ErrorCode<>(errorCode.getCode(), errorCode.getMessage(), id)); + } + } + } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java b/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java index d75526a0..f951590b 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java @@ -3,6 +3,8 @@ import com.mapbefine.mapbefine.auth.infrastructure.JwtTokenProvider; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.exception.MemberErrorCode; +import com.mapbefine.mapbefine.member.exception.MemberException.MemberForbiddenException; import com.mapbefine.mapbefine.oauth.domain.AuthCodeRequestUrlProviderComposite; import com.mapbefine.mapbefine.oauth.domain.OauthMember; import com.mapbefine.mapbefine.oauth.domain.OauthMemberClientComposite; @@ -39,6 +41,8 @@ public LoginInfoResponse login(OauthServerType oauthServerType, String code) { Member savedMember = memberRepository.findByOauthId(oauthMember.getOauthId()) .orElseGet(() -> register(oauthMember)); + validateMemberStatus(savedMember); + String accessToken = jwtTokenProvider.createToken(String.valueOf(savedMember.getId())); return LoginInfoResponse.of(accessToken, savedMember); @@ -49,4 +53,12 @@ private Member register(OauthMember oauthMember) { return memberRepository.save(oauthMember.toRegisterMember()); } + private void validateMemberStatus(Member member) { + if (member.isNormalStatus()) { + return; + } + + throw new MemberForbiddenException(MemberErrorCode.FORBIDDEN_MEMBER_STATUS, member.getId()); + } + } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java b/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java index d6943a99..e31c50d1 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java @@ -5,6 +5,7 @@ import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.OauthId; import com.mapbefine.mapbefine.member.domain.Role; +import com.mapbefine.mapbefine.member.domain.Status; import com.mapbefine.mapbefine.oauth.domain.OauthServerType; import com.mapbefine.mapbefine.topic.domain.Topic; @@ -16,6 +17,7 @@ public static Member create(String name, String email, Role role) { email, "https://map-befine-official.github.io/favicon.png", role, + Status.NORMAL, new OauthId(1L, OauthServerType.KAKAO) ); } @@ -26,6 +28,7 @@ public static Member createWithOauthId(String name, String email, Role role, Oau email, "https://map-befine-official.github.io/favicon.png", role, + Status.NORMAL, oauthId) ; } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberInfoTest.java b/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberInfoTest.java index 7c4d4a20..1ed6e737 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberInfoTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberInfoTest.java @@ -22,6 +22,7 @@ class Validate { private final String VALID_EMAIL = "member@naver.com"; private final String VALID_IMAGE_URL = "https://map-befine-official.github.io/favicon.png"; private final Role VALID_ROLE = Role.ADMIN; + private final Status VALID_STATUS = Status.NORMAL; @Test @DisplayName("정확한 값을 입력하면 객체가 생성된다") @@ -31,7 +32,8 @@ void success() { VALID_NICK_NAME, VALID_EMAIL, VALID_IMAGE_URL, - VALID_ROLE + VALID_ROLE, + VALID_STATUS ); //then @@ -52,7 +54,8 @@ void whenNameIsInvalid_thenFail(String invalidNickName) { invalidNickName, VALID_EMAIL, VALID_IMAGE_URL, - VALID_ROLE + VALID_ROLE, + VALID_STATUS )).isInstanceOf(MemberBadRequestException.class); } @@ -67,7 +70,8 @@ void whenEmailIsInvalid_thenFail(String invalidEmail) { VALID_NICK_NAME, invalidEmail, VALID_IMAGE_URL, - VALID_ROLE + VALID_ROLE, + VALID_STATUS )).isInstanceOf(MemberBadRequestException.class); } @@ -81,7 +85,8 @@ void whenImageUrlIsInvalid_thenFail() { VALID_NICK_NAME, VALID_EMAIL, invalidImageUrl, - VALID_ROLE + VALID_ROLE, + VALID_STATUS )).isInstanceOf(ImageBadRequestException.class); } @@ -94,6 +99,7 @@ void whenRoleIsInvalid_thenFail() { VALID_EMAIL, VALID_IMAGE_URL, null + ,VALID_STATUS )).isInstanceOf(IllegalArgumentException.class); } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberTest.java b/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberTest.java index 0ee9b697..7fcb4b10 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/member/domain/MemberTest.java @@ -16,6 +16,7 @@ void createMember_success() { String email = "member@naver.com"; String imageUrl = "https://map-befine-official.github.io/favicon.png"; Role role = Role.ADMIN; + Status status = Status.NORMAL; // when Member member = Member.of( @@ -23,7 +24,9 @@ void createMember_success() { email, imageUrl, role, - new OauthId(1L, OauthServerType.KAKAO)); + status, + new OauthId(1L, OauthServerType.KAKAO) + ); // then assertThat(member).isNotNull(); From c7c8ee8176898702abb988ed29595462cdce1037 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 15:37:31 +0900 Subject: [PATCH 10/25] =?UTF-8?q?refactor:=20@SqlDelete=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20JPQL=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 20 ++++++++++++++++--- .../admin/application/AdminQueryService.java | 6 +++--- .../admin/presentation/AdminController.java | 2 +- .../application/MemberQueryService.java | 1 + .../mapbefine/member/domain/Member.java | 10 ++++++++++ .../mapbefine/mapbefine/pin/domain/Pin.java | 2 -- .../mapbefine/pin/domain/PinImage.java | 2 -- .../pin/domain/PinImageRepository.java | 5 ++++- .../mapbefine/pin/domain/PinRepository.java | 4 ++++ .../mapbefine/topic/domain/Topic.java | 2 -- .../topic/domain/TopicRepository.java | 4 ++++ 11 files changed, 44 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index 1336fb9b..71126f55 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -6,9 +6,11 @@ import com.mapbefine.mapbefine.auth.domain.AuthMember; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.domain.Status; import com.mapbefine.mapbefine.member.exception.MemberErrorCode; import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; +import com.mapbefine.mapbefine.pin.domain.Pin; import com.mapbefine.mapbefine.pin.domain.PinImageRepository; import com.mapbefine.mapbefine.pin.domain.PinRepository; import com.mapbefine.mapbefine.topic.domain.Topic; @@ -38,13 +40,18 @@ public AdminCommandService( this.pinImageRepository = pinImageRepository; } - // TODO: 2023/09/12 블랙리스트..? - public void deleteMember(AuthMember authMember, Long memberId) { + public void blockMember(AuthMember authMember, Long memberId) { Member admin = findMemberById(authMember.getMemberId()); validateAdminPermission(admin); - memberRepository.deleteById(memberId); + Member member = findMemberById(memberId); + member.updateStatus(Status.BLOCKED); + List pinIds = extractPinIdsByMember(member); + + topicRepository.deleteAllByMemberId(memberId); + pinRepository.deleteAllByMemberId(memberId); + pinImageRepository.deleteAllByPinIds(pinIds); } private Member findMemberById(Long id) { @@ -60,6 +67,13 @@ private void validateAdminPermission(Member member) { throw new PermissionForbiddenException(PERMISSION_FORBIDDEN_BY_NOT_ADMIN); } + private List extractPinIdsByMember(Member member) { + return member.getCreatedPins() + .stream() + .map(Pin::getId) + .toList(); + } + public void deleteTopic(AuthMember authMember, Long topicId) { Member member = findMemberById(authMember.getMemberId()); diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java index 6d96ebbe..87b29187 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -48,14 +48,14 @@ private void validateAdminPermission(Member member) { } public AdminMemberDetailResponse findMemberDetail(AuthMember authMember, Long memberId) { - Member member = findMemberById(authMember.getMemberId()); + Member admin = findMemberById(authMember.getMemberId()); - validateAdminPermission(member); + validateAdminPermission(admin); Member findMember = findMemberById(memberId); List topics = findMember.getCreatedTopics(); List pins = findMember.getCreatedPins(); - return AdminMemberDetailResponse.of(member, topics, pins); + return AdminMemberDetailResponse.of(findMember, topics, pins); } } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java index cccc3f2f..49d503e7 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java @@ -36,7 +36,7 @@ public ResponseEntity> findAllMembers(AuthMember authM @DeleteMapping("/members/{memberId}") public ResponseEntity deleteMember(AuthMember authMember, @PathVariable Long memberId) { - adminCommandService.deleteMember(authMember, memberId); + adminCommandService.blockMember(authMember, memberId); return ResponseEntity.noContent().build(); } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/application/MemberQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/member/application/MemberQueryService.java index c032cf36..df5296f9 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/application/MemberQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/application/MemberQueryService.java @@ -37,6 +37,7 @@ private Member findMemberById(Long id) { .orElseThrow(() -> new MemberNotFoundException(MemberErrorCode.MEMBER_NOT_FOUND, id)); } + // TODO: 2023/09/13 차단된 or 탈퇴한 사용자 필터링 필요 public List findAll() { return memberRepository.findAll() .stream() diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java index fc0a2bb2..1d64325e 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/Member.java @@ -155,4 +155,14 @@ public List getTopicsWithPermissions() { public boolean isNormalStatus() { return memberInfo.getStatus() == Status.NORMAL; } + + public void updateStatus(Status status) { + memberInfo = MemberInfo.of( + memberInfo.getNickName(), + memberInfo.getEmail(), + memberInfo.getImageUrl(), + memberInfo.getRole(), + status + ); + } } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java index e36263cf..194bcaf1 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/Pin.java @@ -22,10 +22,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.SQLDelete; @Entity -@SQLDelete(sql = "UPDATE pin SET is_deleted = true WHERE id = ?") @NoArgsConstructor(access = PROTECTED) @Getter public class Pin extends BaseTimeEntity { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java index 63acb9d8..4da68c7d 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImage.java @@ -14,10 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.SQLDelete; @Entity -@SQLDelete(sql = "UPDATE pin_image SET is_deleted = true WHERE id = ?") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class PinImage extends BaseTimeEntity { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImageRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImageRepository.java index 36f53800..6e25d0af 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImageRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinImageRepository.java @@ -18,6 +18,9 @@ public interface PinImageRepository extends JpaRepository { @Query("update PinImage p set p.isDeleted = true where p.id = :id") void deleteById(@Param("id") Long id); + @Modifying(clearAutomatically = true) + @Query("update PinImage p set p.isDeleted = true where p.pin.id in :pinIds") + void deleteAllByPinIds(@Param("pinIds") List pinIds); + List findAllByPinId(Long pinId); - } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java index 11a2d95d..f706db9e 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java @@ -18,6 +18,10 @@ public interface PinRepository extends JpaRepository { @Query("update Pin p set p.isDeleted = true where p.id = :pinId") void deleteById(@Param("pinId") Long pinId); + @Modifying(clearAutomatically = true) + @Query("update Pin p set p.isDeleted = true where p.creator.id = :memberId") + void deleteAllByMemberId(@Param("memberId") Long memberId); + List findAllByTopicId(Long topicId); List findByCreatorId(Long creatorId); diff --git a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java index f31a6f28..a41c807e 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java @@ -22,10 +22,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.SQLDelete; @Entity -@SQLDelete(sql = "UPDATE topic SET is_deleted = true WHERE id = ?") @NoArgsConstructor(access = PROTECTED) @Getter public class Topic extends BaseTimeEntity { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java index 1ef9fb6f..e54fa236 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java @@ -16,6 +16,10 @@ public interface TopicRepository extends JpaRepository { @Query("update Topic t set t.isDeleted = true where t.id = :topicId") void deleteById(@Param("topicId") Long topicId); + @Modifying(clearAutomatically = true) + @Query("update Topic t set t.isDeleted = true where t.creator.id = :memberId") + void deleteAllByMemberId(@Param("memberId") Long memberId); + boolean existsById(Long id); List findByCreatorId(Long creatorId); From 7f2c47b62469714ac00d824b67c5bc6c844d8914 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 16:40:07 +0900 Subject: [PATCH 11/25] =?UTF-8?q?feat:=20AdminInterceptor=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/presentation/AdminController.java | 1 - .../auth/application/AuthService.java | 16 ++++ .../mapbefine/common/config/AuthConfig.java | 9 ++- .../interceptor/AdminAuthInterceptor.java | 76 +++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java index 49d503e7..2a34fec1 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/presentation/AdminController.java @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -// TODO: 2023/09/12 컨트롤러 자체에서 검증할 수 있는 방법은 없을까 ? @RestController @RequestMapping("/admin") public class AdminController { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java index 538e1244..ce7c3a20 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java @@ -5,6 +5,8 @@ import com.mapbefine.mapbefine.auth.domain.member.User; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.exception.MemberErrorCode; +import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; import com.mapbefine.mapbefine.topic.domain.Topic; import java.util.List; import java.util.Objects; @@ -60,4 +62,18 @@ private List getCreatedTopics(Member member) { .toList(); } + public boolean isAdmin(Long memberId) { + if (Objects.isNull(memberId)) { + return false; + } + + Member member = findMember(memberId); + + return member.isAdmin(); + } + + private Member findMember(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new MemberNotFoundException(MemberErrorCode.MEMBER_NOT_FOUND, memberId)); + } } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/config/AuthConfig.java b/backend/src/main/java/com/mapbefine/mapbefine/common/config/AuthConfig.java index 02cb0062..c972f561 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/common/config/AuthConfig.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/config/AuthConfig.java @@ -1,5 +1,6 @@ package com.mapbefine.mapbefine.common.config; +import com.mapbefine.mapbefine.common.interceptor.AdminAuthInterceptor; import com.mapbefine.mapbefine.common.interceptor.AuthInterceptor; import com.mapbefine.mapbefine.common.resolver.AuthArgumentResolver; import java.util.List; @@ -11,20 +12,26 @@ @Configuration public class AuthConfig implements WebMvcConfigurer { + private final AdminAuthInterceptor adminAuthInterceptor; private final AuthInterceptor authInterceptor; private final AuthArgumentResolver authArgumentResolver; public AuthConfig( + AdminAuthInterceptor adminAuthInterceptor, AuthInterceptor authInterceptor, AuthArgumentResolver authArgumentResolver ) { + this.adminAuthInterceptor = adminAuthInterceptor; this.authInterceptor = authInterceptor; this.authArgumentResolver = authArgumentResolver; } @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(authInterceptor); + registry.addInterceptor(authInterceptor) + .excludePathPatterns("/admin/**"); + registry.addInterceptor(adminAuthInterceptor) + .addPathPatterns("/admin/**"); } @Override diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java b/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java new file mode 100644 index 00000000..93a0cebe --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java @@ -0,0 +1,76 @@ +package com.mapbefine.mapbefine.common.interceptor; + +import com.mapbefine.mapbefine.auth.application.AuthService; +import com.mapbefine.mapbefine.auth.dto.AuthInfo; +import com.mapbefine.mapbefine.auth.infrastructure.AuthorizationExtractor; +import com.mapbefine.mapbefine.auth.infrastructure.JwtTokenProvider; +import com.mapbefine.mapbefine.common.exception.ErrorCode; +import com.mapbefine.mapbefine.common.exception.ForbiddenException; +import com.mapbefine.mapbefine.common.exception.UnauthorizedException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Objects; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class AdminAuthInterceptor implements HandlerInterceptor { + + private static final String UNAUTHORIZED_ERROR_MESSAGE = "로그인에 실패하였습니다."; + private static final ErrorCode ILLEGAL_TOKEN = new ErrorCode("03101", UNAUTHORIZED_ERROR_MESSAGE); + private static final ErrorCode FORBIDDEN_ADMIN_ACCESS = new ErrorCode("03102", UNAUTHORIZED_ERROR_MESSAGE); + + private final AuthorizationExtractor authorizationExtractor; + private final AuthService authService; + private final JwtTokenProvider jwtTokenProvider; + + public AdminAuthInterceptor( + AuthorizationExtractor authorizationExtractor, + AuthService authService, + JwtTokenProvider jwtTokenProvider + ) { + this.authorizationExtractor = authorizationExtractor; + this.authService = authService; + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public boolean preHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler + ) throws Exception { + if (!(handler instanceof HandlerMethod)) { + return true; + } + + Long memberId = extractMemberIdFromToken(request); + + validateAdmin(memberId); + request.setAttribute("memberId", memberId); + + return true; + } + + private Long extractMemberIdFromToken(HttpServletRequest request) { + AuthInfo authInfo = authorizationExtractor.extract(request); + if (Objects.isNull(authInfo)) { + return null; + } + String accessToken = authInfo.accessToken(); + if (jwtTokenProvider.validateToken(accessToken)) { + return Long.parseLong(jwtTokenProvider.getPayload(accessToken)); + } + throw new UnauthorizedException(ILLEGAL_TOKEN); + } + + private void validateAdmin(Long memberId) { + if (authService.isAdmin(memberId)) { + return; + } + + throw new ForbiddenException(FORBIDDEN_ADMIN_ACCESS); + } + +} From 523ce2cf2ebf542f05ce86bc1877b44bc64a670d Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 17:40:58 +0900 Subject: [PATCH 12/25] =?UTF-8?q?test:=20Repository=20soft-deleting=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pin/application/PinQueryService.java | 2 +- .../mapbefine/pin/domain/PinRepository.java | 2 +- .../topic/application/TopicQueryService.java | 4 +- .../topic/domain/TopicRepository.java | 2 +- .../pin/domain/PinImageRepositoryTest.java | 27 ++++++++++ .../pin/domain/PinRepositoryTest.java | 50 +++++++++++++++++-- .../topic/domain/TopicRepositoryTest.java | 36 ++++++++----- 7 files changed, 101 insertions(+), 22 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/application/PinQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/application/PinQueryService.java index 6956b55d..0e2aa2bf 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/application/PinQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/application/PinQueryService.java @@ -52,7 +52,7 @@ private void validateReadAuth(AuthMember member, Topic topic) { } public List findAllPinsByMemberId(AuthMember authMember, Long memberId) { - return pinRepository.findByCreatorId(memberId) + return pinRepository.findAllByCreatorId(memberId) .stream() .filter(pin -> authMember.canRead(pin.getTopic())) .map(PinResponse::from) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java index f706db9e..60d29dc9 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/pin/domain/PinRepository.java @@ -24,7 +24,7 @@ public interface PinRepository extends JpaRepository { List findAllByTopicId(Long topicId); - List findByCreatorId(Long creatorId); + List findAllByCreatorId(Long creatorId); List findAllByOrderByUpdatedAtDesc(); } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/topic/application/TopicQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/topic/application/TopicQueryService.java index aa19d281..de0efdda 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/topic/application/TopicQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/topic/application/TopicQueryService.java @@ -186,7 +186,7 @@ private void validateReadableTopics(AuthMember member, List topics) { public List findAllTopicsByMemberId(AuthMember authMember, Long memberId) { if (Objects.isNull(authMember.getMemberId())) { - return topicRepository.findByCreatorId(memberId) + return topicRepository.findAllByCreatorId(memberId) .stream() .filter(authMember::canRead) .map(TopicResponse::fromGuestQuery) @@ -198,7 +198,7 @@ public List findAllTopicsByMemberId(AuthMember authMember, Long m List topicsInAtlas = findTopicsInAtlas(member); List topicsInBookMark = findBookMarkedTopics(member); - return topicRepository.findByCreatorId(memberId) + return topicRepository.findAllByCreatorId(memberId) .stream() .filter(authMember::canRead) .map(topic -> TopicResponse.from( diff --git a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java index e54fa236..e6eb95db 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicRepository.java @@ -22,6 +22,6 @@ public interface TopicRepository extends JpaRepository { boolean existsById(Long id); - List findByCreatorId(Long creatorId); + List findAllByCreatorId(Long creatorId); } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinImageRepositoryTest.java b/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinImageRepositoryTest.java index ad36bb0d..a8604271 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinImageRepositoryTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinImageRepositoryTest.java @@ -14,6 +14,7 @@ import com.mapbefine.mapbefine.topic.TopicFixture; import com.mapbefine.mapbefine.topic.domain.Topic; import com.mapbefine.mapbefine.topic.domain.TopicRepository; +import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -87,4 +88,30 @@ void deleteAllByPinId_Success() { .extractingResultOf("isDeleted") .containsOnly(true); } + + @Test + @DisplayName("여러 핀을 Id로 삭제하면, 핀 이미지들도 soft-deleting 된다.") + void deleteAllByMemberId_Success() { + //given + Pin otherPin = pinRepository.save(PinFixture.create(pin.getLocation(), topic, member)); + + PinImage pinImage1 = PinImageFixture.create(pin); + PinImage pinImage2 = PinImageFixture.create(otherPin); + pinImageRepository.save(pinImage1); + pinImageRepository.save(pinImage2); + + //when + assertThat(pinImage1.isDeleted()).isFalse(); + assertThat(pinImage2.isDeleted()).isFalse(); + pinImageRepository.deleteAllByPinIds(List.of(pin.getId(),otherPin.getId())); + + //then + assertThat(pinImageRepository.findAllByPinId(pin.getId())) + .extractingResultOf("isDeleted") + .containsOnly(true); + assertThat(pinImageRepository.findAllByPinId(otherPin.getId())) + .extractingResultOf("isDeleted") + .containsOnly(true); + } + } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinRepositoryTest.java b/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinRepositoryTest.java index 508d938e..24bf7cde 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinRepositoryTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/pin/domain/PinRepositoryTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import com.mapbefine.mapbefine.location.LocationFixture; -import com.mapbefine.mapbefine.location.domain.Coordinate; import com.mapbefine.mapbefine.location.domain.Location; import com.mapbefine.mapbefine.location.domain.LocationRepository; import com.mapbefine.mapbefine.member.MemberFixture; @@ -24,10 +23,6 @@ @DataJpaTest class PinRepositoryTest { - private static final Coordinate DEFAULT_COORDINATE = Coordinate.of( - 37.5152933, - 127.1029866 - ); @Autowired private TopicRepository topicRepository; @@ -88,4 +83,49 @@ void deleteAllByTopicId_Success() { .containsOnly(true); } + + @Test + @DisplayName("Member ID로 모든 핀을 soft-delete 할 수 있다.") + void deleteAllByMemberId_Success() { + //given + for (int i = 0; i < 10; i++) { + pinRepository.save(PinFixture.create(location, topic, member)); + } + + //when + assertThat(member.getCreatedPins()).hasSize(10) + .extractingResultOf("isDeleted") + .containsOnly(false); + pinRepository.deleteAllByMemberId(member.getId()); + + //then + List deletedPins = pinRepository.findAllByCreatorId(member.getId()); + assertThat(deletedPins).extractingResultOf("isDeleted") + .containsOnly(true); + } + + @Test + @DisplayName("다른 토픽에 존재하는 핀들이여도, Member ID로 모든 핀을 soft-delete 할 수 있다.") + void deleteAllByMemberIdInOtherTopics_Success() { + //given + Topic otherTopic = TopicFixture.createByName("otherTopic", member); + topicRepository.save(otherTopic); + + for (int i = 0; i < 10; i++) { + pinRepository.save(PinFixture.create(location, topic, member)); + pinRepository.save(PinFixture.create(location, otherTopic, member)); + } + + //when + assertThat(member.getCreatedPins()).hasSize(20) + .extractingResultOf("isDeleted") + .containsOnly(false); + pinRepository.deleteAllByMemberId(member.getId()); + + //then + List deletedPins = pinRepository.findAllByCreatorId(member.getId()); + assertThat(deletedPins).extractingResultOf("isDeleted") + .containsOnly(true); + } + } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/topic/domain/TopicRepositoryTest.java b/backend/src/test/java/com/mapbefine/mapbefine/topic/domain/TopicRepositoryTest.java index 3280df30..2a783b70 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/topic/domain/TopicRepositoryTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/topic/domain/TopicRepositoryTest.java @@ -6,6 +6,8 @@ import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; import com.mapbefine.mapbefine.member.domain.Role; +import com.mapbefine.mapbefine.topic.TopicFixture; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,24 +27,14 @@ class TopicRepositoryTest { @BeforeEach void setUp() { - member = MemberFixture.create("member", "member@naver.com", Role.USER); - memberRepository.save(member); + member = memberRepository.save(MemberFixture.create("member", "member@naver.com", Role.USER)); } @Test @DisplayName("토픽을 삭제하면, soft-deleting 된다.") void deleteById_Success() { //given - Topic topic = Topic.createTopicAssociatedWithCreator( - "토픽", - "토픽설명", - "https://example.com/image.jpg", - Publicity.PUBLIC, - PermissionType.ALL_MEMBERS, - member - ); - - topicRepository.save(topic); + Topic topic = topicRepository.save(TopicFixture.createByName("Topic", member)); assertThat(topic.isDeleted()).isFalse(); @@ -54,4 +46,24 @@ void deleteById_Success() { assertThat(deletedTopic.isDeleted()).isTrue(); } + @Test + @DisplayName("Member Id로 모든 토픽을 삭제하면, soft-deleting 된다.") + void deleteAllByMemberId_Success() { + //given + for (int i = 0; i < 10; i++) { + topicRepository.save(TopicFixture.createByName("topic" + i, member)); + } + assertThat(member.getCreatedTopics()).hasSize(10) + .extractingResultOf("isDeleted") + .containsOnly(false); + + //when + topicRepository.deleteAllByMemberId(member.getId()); + + //then + List deletedTopics = topicRepository.findAllByCreatorId(member.getId()); + assertThat(deletedTopics).hasSize(10) + .extractingResultOf("isDeleted") + .containsOnly(true); + } } From 0e887ea820effe06f5373771e5ad4674c8e83410 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 20:26:51 +0900 Subject: [PATCH 13/25] =?UTF-8?q?test:=20AdminQueryService=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/application/AdminQueryService.java | 3 +- .../member/domain/MemberRepository.java | 3 + .../application/AdminQueryServiceTest.java | 135 ++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminQueryServiceTest.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java index 87b29187..82862492 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -5,6 +5,7 @@ import com.mapbefine.mapbefine.auth.domain.AuthMember; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.member.exception.MemberErrorCode; import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; import com.mapbefine.mapbefine.permission.exception.PermissionErrorCode; @@ -27,7 +28,7 @@ public List findAllMemberDetails(AuthMember authMember) { Member admin = findMemberById(authMember.getMemberId()); validateAdminPermission(admin); - List members = memberRepository.findAll(); + List members = memberRepository.findAllByMemberInfoRole(Role.USER); return members.stream() .map(AdminMemberResponse::from) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberRepository.java index 16b57c05..65125aa1 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberRepository.java @@ -1,5 +1,6 @@ package com.mapbefine.mapbefine.member.domain; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -13,4 +14,6 @@ public interface MemberRepository extends JpaRepository { Optional findByOauthId(OauthId oauthId); + List findAllByMemberInfoRole(Role role); + } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminQueryServiceTest.java b/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminQueryServiceTest.java new file mode 100644 index 00000000..33599110 --- /dev/null +++ b/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminQueryServiceTest.java @@ -0,0 +1,135 @@ +package com.mapbefine.mapbefine.admin.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.mapbefine.mapbefine.admin.dto.AdminMemberDetailResponse; +import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse; +import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.common.annotation.ServiceTest; +import com.mapbefine.mapbefine.location.LocationFixture; +import com.mapbefine.mapbefine.location.domain.Location; +import com.mapbefine.mapbefine.location.domain.LocationRepository; +import com.mapbefine.mapbefine.member.MemberFixture; +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.domain.Role; +import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; +import com.mapbefine.mapbefine.pin.PinFixture; +import com.mapbefine.mapbefine.pin.domain.Pin; +import com.mapbefine.mapbefine.pin.domain.PinRepository; +import com.mapbefine.mapbefine.topic.TopicFixture; +import com.mapbefine.mapbefine.topic.domain.Topic; +import com.mapbefine.mapbefine.topic.domain.TopicRepository; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@ServiceTest +class AdminQueryServiceTest { + + @Autowired + private AdminQueryService adminQueryService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private TopicRepository topicRepository; + + @Autowired + private PinRepository pinRepository; + + @Autowired + private LocationRepository locationRepository; + + private Location location; + private Member admin; + private Member member; + private Topic topic; + private Pin pin; + + @BeforeEach + void setup() { + admin = memberRepository.save(MemberFixture.create("Admin", "admin@naver.com", Role.ADMIN)); + member = memberRepository.save(MemberFixture.create("member", "member@gmail.com", Role.USER)); + topic = topicRepository.save(TopicFixture.createByName("topic", member)); + location = locationRepository.save(LocationFixture.create()); + pin = pinRepository.save(PinFixture.create(location, topic, member)); + } + + @Test + @DisplayName("사용자와 관련된 세부(민감한 정보 포함) 정보를 모두 조회할 수 있다.") + void findMemberDetail_Success() { + //given + AuthMember adminAuthMember = MemberFixture.createUser(admin); + + //when + AdminMemberDetailResponse response = adminQueryService.findMemberDetail(adminAuthMember, member.getId()); + + //then + assertThat(response).usingRecursiveComparison() + .ignoringFields("updatedAt") + .isEqualTo(AdminMemberDetailResponse.of(member, List.of(topic), List.of(pin))); + } + + @Test + @DisplayName("Admin이 아닌 경우, 사용자와 관련된 세부(민감한 정보 포함) 정보를 모두 조회할 수 없다.") + void findMemberDetail_Fail() { + //given + AuthMember userAuthMember = MemberFixture.createUser(member); + + //when //then + Long memberId = member.getId(); + assertThatThrownBy(() -> adminQueryService.findMemberDetail(userAuthMember, memberId)) + .isInstanceOf(PermissionForbiddenException.class); + } + + @Test + @DisplayName("모든 사용자와 관련된 세부 정보를 모두 조회할 수 있다.") + void findAllMemberDetails_Success() { + //given + AuthMember adminAuthMember = MemberFixture.createUser(admin); + + ArrayList members = new ArrayList<>(); + members.add(member); + for (int i = 0; i < 10; i++) { + Member saveMember = MemberFixture.create("member" + i, "member" + i + "@email.com", Role.USER); + members.add(memberRepository.save(saveMember)); + } + + //when + List responses = adminQueryService.findAllMemberDetails(adminAuthMember); + //then + List expected = members.stream() + .map(AdminMemberResponse::from) + .toList(); + + assertThat(responses).usingRecursiveComparison() + .ignoringFields("updatedAt") + .ignoringCollectionOrderInFields() + .isEqualTo(expected); + } + + @Test + @DisplayName("Admin이 아닌 경우 모든 사용자와 관련된 세부 정보를 모두 조회할 수 없다.") + void findAllMemberDetails_Fail() { + //given + AuthMember memberAuthMember = MemberFixture.createUser(member); + + ArrayList members = new ArrayList<>(); + members.add(member); + for (int i = 0; i < 10; i++) { + Member saveMember = MemberFixture.create("member" + i, "member" + i + "@email.com", Role.USER); + members.add(memberRepository.save(saveMember)); + } + + //when //then + assertThatThrownBy(() -> adminQueryService.findAllMemberDetails(memberAuthMember)) + .isInstanceOf(PermissionForbiddenException.class); + } + +} \ No newline at end of file From 190f5d39a979e24c30e88df3e60007bec0803c9c Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 21:24:49 +0900 Subject: [PATCH 14/25] =?UTF-8?q?test:=20AdminCommandService=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 5 +- .../mapbefine/topic/domain/Topic.java | 4 + .../mapbefine/topic/domain/TopicInfo.java | 9 + .../application/AdminCommandServiceTest.java | 251 ++++++++++++++++++ 4 files changed, 265 insertions(+), 4 deletions(-) create mode 100644 backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index 71126f55..ea3f4751 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -14,7 +14,6 @@ import com.mapbefine.mapbefine.pin.domain.PinImageRepository; import com.mapbefine.mapbefine.pin.domain.PinRepository; import com.mapbefine.mapbefine.topic.domain.Topic; -import com.mapbefine.mapbefine.topic.domain.TopicInfo; import com.mapbefine.mapbefine.topic.domain.TopicRepository; import com.mapbefine.mapbefine.topic.exception.TopicException; import java.util.List; @@ -88,9 +87,7 @@ public void deleteTopicImage(AuthMember authMember, Long topicId) { validateAdminPermission(member); Topic topic = findTopicById(topicId); - TopicInfo topicInfo = topic.getTopicInfo(); - - topic.updateTopicInfo(topicInfo.getName(), topicInfo.getDescription(), ""); + topic.removeImage(); } private Topic findTopicById(Long topicId) { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java index a41c807e..d5128022 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/Topic.java @@ -114,4 +114,8 @@ public int countBookmarks() { return bookmarks.size(); } + public void removeImage() { + this.topicInfo = topicInfo.removeImage(); + } + } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicInfo.java b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicInfo.java index 3f5ffd59..dff5da7e 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicInfo.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/topic/domain/TopicInfo.java @@ -88,4 +88,13 @@ private static Image createImage(String imageUrl) { public String getImageUrl() { return image.getImageUrl(); } + + public TopicInfo removeImage() { + return new TopicInfo( + this.name, + this.description, + DEFAULT_IMAGE + ); + } + } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java b/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java new file mode 100644 index 00000000..e490ab2d --- /dev/null +++ b/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java @@ -0,0 +1,251 @@ +package com.mapbefine.mapbefine.admin.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.common.annotation.ServiceTest; +import com.mapbefine.mapbefine.location.LocationFixture; +import com.mapbefine.mapbefine.location.domain.Location; +import com.mapbefine.mapbefine.location.domain.LocationRepository; +import com.mapbefine.mapbefine.member.MemberFixture; +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.domain.Role; +import com.mapbefine.mapbefine.member.domain.Status; +import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; +import com.mapbefine.mapbefine.pin.PinFixture; +import com.mapbefine.mapbefine.pin.PinImageFixture; +import com.mapbefine.mapbefine.pin.domain.Pin; +import com.mapbefine.mapbefine.pin.domain.PinImage; +import com.mapbefine.mapbefine.pin.domain.PinImageRepository; +import com.mapbefine.mapbefine.pin.domain.PinRepository; +import com.mapbefine.mapbefine.topic.TopicFixture; +import com.mapbefine.mapbefine.topic.domain.Topic; +import com.mapbefine.mapbefine.topic.domain.TopicInfo; +import com.mapbefine.mapbefine.topic.domain.TopicRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@ServiceTest +class AdminCommandServiceTest { + + @Autowired + private AdminCommandService adminCommandService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private TopicRepository topicRepository; + + @Autowired + private PinRepository pinRepository; + + @Autowired + private LocationRepository locationRepository; + + @Autowired + private PinImageRepository pinImageRepository; + private Location location; + private Member admin; + private Member member; + private Topic topic; + private Pin pin; + private PinImage pinImage; + + @BeforeEach + void setup() { + admin = memberRepository.save(MemberFixture.create("Admin", "admin@naver.com", Role.ADMIN)); + member = memberRepository.save(MemberFixture.create("member", "member@gmail.com", Role.USER)); + topic = topicRepository.save(TopicFixture.createByName("topic", member)); + location = locationRepository.save(LocationFixture.create()); + pin = pinRepository.save(PinFixture.create(location, topic, member)); + pinImage = pinImageRepository.save(PinImageFixture.create(pin)); + } + + @DisplayName("Member를 차단(탈퇴시킬)할 경우, Member가 생성한 토픽, 핀, 핀 이미지가 soft-deleting 된다.") + @Test + void blockMember_Success() { + //given + AuthMember adminAuthMember = MemberFixture.createUser(admin); + + assertAll(() -> { + assertThat(member.getMemberInfo().getStatus()).isEqualTo(Status.NORMAL); + assertThat(topic.isDeleted()).isFalse(); + assertThat(pin.isDeleted()).isFalse(); + assertThat(pinImage.isDeleted()).isFalse(); + }); + + //when + adminCommandService.blockMember(adminAuthMember, member.getId()); + + //then + Topic deletedTopic = topicRepository.findById(topic.getId()).get(); + Pin deletedPin = pinRepository.findById(pin.getId()).get(); + PinImage deletedPinImage = pinImageRepository.findById(pinImage.getId()).get(); + + assertAll(() -> { + assertThat(member.getMemberInfo().getStatus()).isEqualTo(Status.BLOCKED); + assertThat(deletedTopic.isDeleted()).isTrue(); + assertThat(deletedPin.isDeleted()).isTrue(); + assertThat(deletedPinImage.isDeleted()).isTrue(); + }); + } + + @DisplayName("Admin이 아닐 경우, Member를 차단(탈퇴시킬)할 수 없다.") + @Test + void blockMember_Fail() { + //given + AuthMember userAuthMember = MemberFixture.createUser(member); + Member otherMember = MemberFixture.create("otherMember", "otherMember@email.com", Role.USER); + memberRepository.save(otherMember); + + assertAll(() -> { + assertThat(member.getMemberInfo().getStatus()).isEqualTo(Status.NORMAL); + assertThat(topic.isDeleted()).isFalse(); + assertThat(pin.isDeleted()).isFalse(); + assertThat(pinImage.isDeleted()).isFalse(); + }); + + //when then + Long otherMemberId = otherMember.getId(); + assertThatThrownBy(() -> adminCommandService.blockMember(userAuthMember, otherMemberId)) + .isInstanceOf(PermissionForbiddenException.class); + } + + @DisplayName("Admin은 토픽을 삭제시킬 수 있다.") + @Test + void deleteTopic_Success() { + //given + AuthMember adminAuthMember = MemberFixture.createUser(admin); + + assertThat(topic.isDeleted()).isFalse(); + + //when + adminCommandService.deleteTopic(adminAuthMember, topic.getId()); + + //then + Topic deletedTopic = topicRepository.findById(topic.getId()).get(); + + assertThat(deletedTopic.isDeleted()).isTrue(); + } + + @DisplayName("Admin이 아닐 경우, 토픽을 삭제시킬 수 없다.") + @Test + void deleteTopic_Fail() { + //given + AuthMember userAuthMember = MemberFixture.createUser(member); + + assertThat(topic.isDeleted()).isFalse(); + + //when then + Long topicId = topic.getId(); + assertThatThrownBy(() -> adminCommandService.deleteTopic(userAuthMember, topicId)) + .isInstanceOf(PermissionForbiddenException.class); + } + + @DisplayName("Admin은 토픽 이미지를 삭제할 수 있다.") + @Test + void deleteTopicImage_Success() { + //given + AuthMember adminAuthMember = MemberFixture.createUser(admin); + TopicInfo topicInfo = topic.getTopicInfo(); + + topic.updateTopicInfo(topicInfo.getName(), topicInfo.getDescription(), "https://imageUrl.png"); + + assertThat(topic.getTopicInfo().getImageUrl()).isEqualTo("https://imageUrl.png"); + + //when + adminCommandService.deleteTopicImage(adminAuthMember, topic.getId()); + + //then + Topic imageDeletedTopic = topicRepository.findById(topic.getId()).get(); + + assertThat(imageDeletedTopic.getTopicInfo().getImageUrl()) + .isEqualTo("https://map-befine-official.github.io/favicon.png"); + } + + @DisplayName("Admin이 아닐 경우, 이미지를 삭제할 수 없다.") + @Test + void deleteTopicImage_Fail() { + //given + AuthMember userAuthMember = MemberFixture.createUser(member); + TopicInfo topicInfo = topic.getTopicInfo(); + + topic.updateTopicInfo(topicInfo.getName(), topicInfo.getDescription(), "https://imageUrl.png"); + + assertThat(topic.getTopicInfo().getImageUrl()).isEqualTo("https://imageUrl.png"); + + //when then + Long topicId = topic.getId(); + assertThatThrownBy(() -> adminCommandService.deleteTopicImage(userAuthMember, topicId)) + .isInstanceOf(PermissionForbiddenException.class); + } + + @DisplayName("Admin은 핀을 삭제할 수 있다.") + @Test + void deletePin_Success() { + //given + AuthMember adminAuthMember = MemberFixture.createUser(admin); + + assertThat(pin.isDeleted()).isFalse(); + + //when + adminCommandService.deletePin(adminAuthMember, pin.getId()); + + //then + Pin deletedPin = pinRepository.findById(pin.getId()).get(); + + assertThat(deletedPin.isDeleted()).isTrue(); + } + + @DisplayName("Admin이 아닐 경우, 핀을 삭제할 수 없다.") + @Test + void deletePin_Fail() { + //given + AuthMember userAuthMember = MemberFixture.createUser(member); + + assertThat(pin.isDeleted()).isFalse(); + + //when then + Long pinId = pin.getId(); + assertThatThrownBy(() -> adminCommandService.deletePin(userAuthMember, pinId)) + .isInstanceOf(PermissionForbiddenException.class); + } + + @DisplayName("Admin인 경우, 핀 이미지를 삭제할 수 있다.") + @Test + void deletePinImage_Success() { + //given + AuthMember adminAuthMember = MemberFixture.createUser(admin); + + assertThat(pinImage.isDeleted()).isFalse(); + + //when + adminCommandService.deletePinImage(adminAuthMember, pinImage.getId()); + + //then + PinImage deletedPinImage = pinImageRepository.findById(pinImage.getId()).get(); + + assertThat(deletedPinImage.isDeleted()).isTrue(); + } + + @DisplayName("Admin이 아닐 경우, 핀 이미지를 삭제할 수 없다.") + @Test + void deletePinImage_Fail() { + //given + AuthMember userAuthMember = MemberFixture.createUser(member); + + assertThat(pinImage.isDeleted()).isFalse(); + + //when then + Long pinImageId = pinImage.getId(); + assertThatThrownBy(() -> adminCommandService.deletePinImage(userAuthMember, pinImageId)) + .isInstanceOf(PermissionForbiddenException.class); + } + +} \ No newline at end of file From 1932e58377085f6e0913026d849e5ea8eb73cd06 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Wed, 13 Sep 2023 22:42:56 +0900 Subject: [PATCH 15/25] =?UTF-8?q?test:=20AdminController=20Restdocs=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/docs/asciidoc/admin.adoc | 29 +++ backend/src/docs/asciidoc/index.adoc | 1 + .../presentation/AdminControllerTest.java | 180 ++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 backend/src/docs/asciidoc/admin.adoc create mode 100644 backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java diff --git a/backend/src/docs/asciidoc/admin.adoc b/backend/src/docs/asciidoc/admin.adoc new file mode 100644 index 00000000..3d2137c4 --- /dev/null +++ b/backend/src/docs/asciidoc/admin.adoc @@ -0,0 +1,29 @@ +== 관리자 기능 + +=== 전체 회원 조회 + +operation::admin-controller-test/find-all-member-details[snippets='http-request,http-response'] + +=== 회원 상세 조회 + +operation::admin-controller-test/find-member[snippets='http-request,http-response'] + +=== 회원 차단(삭제) + +operation::admin-controller-test/delete-member[snippets='http-request,http-response'] + +=== 토픽 삭제 + +operation::admin-controller-test/delete-topic[snippets='http-request,http-response'] + +=== 토픽 이미지 삭제 + +operation::admin-controller-test/delete-topic-image[snippets='http-request,http-response'] + +=== 핀 삭제 + +operation::admin-controller-test/delete-pin[snippets='http-request,http-response'] + +=== 핀 이미지 삭제 + +operation::admin-controller-test/delete-pin-image[snippets='http-request,http-response'] \ No newline at end of file diff --git a/backend/src/docs/asciidoc/index.adoc b/backend/src/docs/asciidoc/index.adoc index c15272d2..a90d89df 100644 --- a/backend/src/docs/asciidoc/index.adoc +++ b/backend/src/docs/asciidoc/index.adoc @@ -15,3 +15,4 @@ include::member.adoc[] include::permission.adoc[] include::oauth.adoc[] include::bookmark.adoc[] +include::admin.adoc[] diff --git a/backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java b/backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java new file mode 100644 index 00000000..e4ab79d5 --- /dev/null +++ b/backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java @@ -0,0 +1,180 @@ +package com.mapbefine.mapbefine.admin.presentation; + +import static org.apache.http.HttpHeaders.AUTHORIZATION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; + +import com.mapbefine.mapbefine.admin.application.AdminCommandService; +import com.mapbefine.mapbefine.admin.application.AdminQueryService; +import com.mapbefine.mapbefine.admin.dto.AdminMemberDetailResponse; +import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse; +import com.mapbefine.mapbefine.common.RestDocsIntegration; +import com.mapbefine.mapbefine.member.MemberFixture; +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.domain.Role; +import com.mapbefine.mapbefine.pin.dto.response.PinResponse; +import com.mapbefine.mapbefine.topic.dto.response.TopicResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import javax.swing.text.html.Option; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +class AdminControllerTest extends RestDocsIntegration { + + private static final List TOPIC_RESPONSES = List.of(new TopicResponse( + 1L, + "준팍의 또 토픽", + "https://map-befine-official.github.io/favicon.png", + "준팍", + 3, + Boolean.FALSE, + 5, + Boolean.FALSE, + LocalDateTime.now() + ), new TopicResponse( + 2L, + "준팍의 두번째 토픽", + "https://map-befine-official.github.io/favicon.png", + "준팍", + 5, + Boolean.FALSE, + 3, + Boolean.FALSE, + LocalDateTime.now() + )); + private static final List PIN_RESPONSES = List.of( + new PinResponse( + 1L, + "매튜의 산스장", + "지번 주소", + "매튜가 사랑하는 산스장", + "매튜", + 37, + 127 + ), new PinResponse( + 2L, + "매튜의 안갈집", + "지번 주소", + "매튜가 두번은 안 갈 집", + "매튜", + 37, + 127 + ) + ); + + private static final Member ADMIN = MemberFixture.create("Admin", "admin@naver.com", Role.ADMIN); + + @MockBean + private AdminCommandService adminCommandService; + + @MockBean + private AdminQueryService adminQueryService; + + @MockBean + private MemberRepository memberRepository; + + @BeforeEach + void setAll(){ + given(memberRepository.findById(any())).willReturn(Optional.of(ADMIN)); + } + + @DisplayName("멤버 목록 조회") + @Test + void findAllMemberDetails() throws Exception { + List response = List.of( + new AdminMemberResponse(1L, "쥬니", "zuny@naver.com", "https://zuny.png", LocalDateTime.now()), + new AdminMemberResponse(2L, "세인", "semin@naver.com", "https://semin.png", LocalDateTime.now()) + ); + + given(adminQueryService.findAllMemberDetails(any())).willReturn(response); + + mockMvc.perform( + MockMvcRequestBuilders.get("/admin/members") + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeaderById(1L)) + ).andDo(restDocs.document()); + } + + @DisplayName("멤버 상세 조회") + @Test + void findMember() throws Exception { + AdminMemberDetailResponse response = new AdminMemberDetailResponse( + 1L, + "쥬니", + "zuny@naver.com", + "https://image.png", + TOPIC_RESPONSES, + PIN_RESPONSES, + LocalDateTime.now() + ); + + given(adminQueryService.findMemberDetail(any(), any())).willReturn(response); + + mockMvc.perform( + MockMvcRequestBuilders.get("/admin/members/1") + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeaderById(1L)) + ).andDo(restDocs.document()); + } + + @DisplayName("멤버 차단(블랙리스트)") + @Test + void deleteMember() throws Exception { + doNothing().when(adminCommandService).blockMember(any(), any()); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/admin/members/1") + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeaderById(1L)) + ).andDo(restDocs.document()); + } + + @DisplayName("토픽 삭제") + @Test + void deleteTopic() throws Exception { + doNothing().when(adminCommandService).deleteTopic(any(), any()); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/admin/topics/1") + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeaderById(1L)) + ).andDo(restDocs.document()); + } + + @DisplayName("토픽 이미지 삭제") + @Test + void deleteTopicImage() throws Exception { + doNothing().when(adminCommandService).deleteTopicImage(any(), any()); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/admin/topics/1/images") + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeaderById(1L)) + ).andDo(restDocs.document()); + } + + @DisplayName("핀 삭제") + @Test + void deletePin() throws Exception { + doNothing().when(adminCommandService).deletePin(any(), any()); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/admin/pins/1") + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeaderById(1L)) + ).andDo(restDocs.document()); + } + + @DisplayName("토픽 이미지 삭제") + @Test + void deletePinImage() throws Exception { + doNothing().when(adminCommandService).deletePinImage(any(), any()); + + mockMvc.perform( + MockMvcRequestBuilders.delete("/admin/pins/images/1") + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeaderById(1L)) + ).andDo(restDocs.document()); + } +} \ No newline at end of file From bb6fb8da02e484946e0d3202924dc9c2110a0464 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Thu, 14 Sep 2023 17:17:50 +0900 Subject: [PATCH 16/25] test: AdminInterceptor Mocking --- .../admin/presentation/AdminControllerTest.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java b/backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java index e4ab79d5..edceb5d3 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/admin/presentation/AdminControllerTest.java @@ -10,17 +10,11 @@ import com.mapbefine.mapbefine.admin.dto.AdminMemberDetailResponse; import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse; import com.mapbefine.mapbefine.common.RestDocsIntegration; -import com.mapbefine.mapbefine.member.MemberFixture; -import com.mapbefine.mapbefine.member.domain.Member; -import com.mapbefine.mapbefine.member.domain.MemberRepository; -import com.mapbefine.mapbefine.member.domain.Role; +import com.mapbefine.mapbefine.common.interceptor.AdminAuthInterceptor; import com.mapbefine.mapbefine.pin.dto.response.PinResponse; import com.mapbefine.mapbefine.topic.dto.response.TopicResponse; import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; -import javax.swing.text.html.Option; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -70,8 +64,6 @@ class AdminControllerTest extends RestDocsIntegration { ) ); - private static final Member ADMIN = MemberFixture.create("Admin", "admin@naver.com", Role.ADMIN); - @MockBean private AdminCommandService adminCommandService; @@ -79,11 +71,11 @@ class AdminControllerTest extends RestDocsIntegration { private AdminQueryService adminQueryService; @MockBean - private MemberRepository memberRepository; + private AdminAuthInterceptor adminAuthInterceptor; @BeforeEach - void setAll(){ - given(memberRepository.findById(any())).willReturn(Optional.of(ADMIN)); + void setAll() throws Exception { + given(adminAuthInterceptor.preHandle(any(), any(), any())).willReturn(true); } @DisplayName("멤버 목록 조회") From 0f545712909bcaac5948cf4707723c76068db74e Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Thu, 14 Sep 2023 18:47:43 +0900 Subject: [PATCH 17/25] =?UTF-8?q?test:=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 2 + .../admin/application/AdminQueryService.java | 2 + .../mapbefine/admin/AdminIntegrationTest.java | 248 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index ea3f4751..e03f19a3 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -18,8 +18,10 @@ import com.mapbefine.mapbefine.topic.exception.TopicException; import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service +@Transactional public class AdminCommandService { private final MemberRepository memberRepository; diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java index 82862492..e2b4520a 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -14,8 +14,10 @@ import com.mapbefine.mapbefine.topic.domain.Topic; import java.util.List; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service +@Transactional(readOnly = true) public class AdminQueryService { private final MemberRepository memberRepository; diff --git a/backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java b/backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java new file mode 100644 index 00000000..419c68e9 --- /dev/null +++ b/backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java @@ -0,0 +1,248 @@ +package com.mapbefine.mapbefine.admin; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + +import com.mapbefine.mapbefine.admin.dto.AdminMemberDetailResponse; +import com.mapbefine.mapbefine.admin.dto.AdminMemberResponse; +import com.mapbefine.mapbefine.common.IntegrationTest; +import com.mapbefine.mapbefine.location.LocationFixture; +import com.mapbefine.mapbefine.location.domain.Location; +import com.mapbefine.mapbefine.location.domain.LocationRepository; +import com.mapbefine.mapbefine.member.MemberFixture; +import com.mapbefine.mapbefine.member.domain.Member; +import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.domain.Role; +import com.mapbefine.mapbefine.pin.PinFixture; +import com.mapbefine.mapbefine.pin.PinImageFixture; +import com.mapbefine.mapbefine.pin.domain.Pin; +import com.mapbefine.mapbefine.pin.domain.PinImage; +import com.mapbefine.mapbefine.pin.domain.PinImageRepository; +import com.mapbefine.mapbefine.pin.domain.PinRepository; +import com.mapbefine.mapbefine.topic.TopicFixture; +import com.mapbefine.mapbefine.topic.domain.Topic; +import com.mapbefine.mapbefine.topic.domain.TopicRepository; +import io.restassured.common.mapper.TypeRef; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +class AdminIntegrationTest extends IntegrationTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private TopicRepository topicRepository; + + @Autowired + private PinRepository pinRepository; + + @Autowired + private LocationRepository locationRepository; + + @Autowired + private PinImageRepository pinImageRepository; + + private Location location; + private Member admin; + private Member member; + private Topic topic; + private Pin pin; + private PinImage pinImage; + + @BeforeEach + void setup() { + admin = memberRepository.save(MemberFixture.create("Admin", "admin@naver.com", Role.ADMIN)); + member = memberRepository.save(MemberFixture.create("member", "member@gmail.com", Role.USER)); + topic = topicRepository.save(TopicFixture.createByName("topic", member)); + location = locationRepository.save(LocationFixture.create()); + pin = pinRepository.save(PinFixture.create(location, topic, member)); + pinImage = pinImageRepository.save(PinImageFixture.create(pin)); + } + + @Test + @DisplayName("어드민일 경우, 회원을 전체 조회할 수 있다.") + void findAllMembers_Success() { + //given + Member member1 = MemberFixture.create("member1", "member1@gmail.com", Role.USER); + Member member2 = MemberFixture.create("member2", "member2@gmail.com", Role.USER); + memberRepository.save(member1); + memberRepository.save(member2); + + //when + List response = given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(admin)) + .accept(MediaType.APPLICATION_JSON_VALUE) + .when().get("/admin/members") + .then().log().all() + .extract() + .as(new TypeRef<>() { + }); + + //then + List expected = List.of( + AdminMemberResponse.from(member), + AdminMemberResponse.from(member1), + AdminMemberResponse.from(member2) + ); + + assertThat(response).usingRecursiveComparison() + .ignoringFields("updatedAt") + .isEqualTo(expected); + } + + @Test + @DisplayName("어드민이 아닐 경우, 회원을 전체 조회할 수 없다.") + void findAllMembers_Fail() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(member)) + .accept(MediaType.APPLICATION_JSON_VALUE) + .when().get("/admin/members/" + member.getId()) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + @DisplayName("어드민일 경우, 특정 회원의 상세 정보를 조회할 수 있다.") + void findMemberDetail_Success() { + //given when + AdminMemberDetailResponse response = given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(admin)) + .accept(MediaType.APPLICATION_JSON_VALUE) + .when().get("/admin/members/" + member.getId()) + .then().log().all() + .extract() + .as(new TypeRef<>() { + }); + + //then + + AdminMemberDetailResponse expected = AdminMemberDetailResponse.of( + member, + member.getCreatedTopics(), + member.getCreatedPins() + ); + + assertThat(response).usingRecursiveComparison() + .ignoringFields("updatedAt") + .isEqualTo(expected); + } + + @Test + @DisplayName("어드민이 아닐 경우, 특정 회원의 상세 정보를 조회할 수 없다.") + void findMemberDetail_Fail() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(member)) + .accept(MediaType.APPLICATION_JSON_VALUE) + .when().get("/admin/members/" + member.getId()) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + @DisplayName("어드민일 경우, 특정 회원을 삭제(차단)할 수 있다.") + void deleteMember_Success() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(admin)) + .when().delete("/admin/members/" + member.getId()) + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("어드민이 아닐 경우, 특정 회원을 삭제(차단)할 수 없다.") + void deleteMember_Fail() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(member)) + .when().delete("/admin/members/" + member.getId()) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + @DisplayName("어드민일 경우, 특정 토픽을 삭제할 수 있다.") + void deleteTopic_Success() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(admin)) + .when().delete("/admin/topics/" + topic.getId()) + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("어드민이 아닐 경우, 특정 토픽을 삭제할 수 없다.") + void deleteTopic_Fail() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(member)) + .when().delete("/admin/topics/" + topic.getId()) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + @DisplayName("어드민일 경우, 특정 토픽 이미지를 삭제할 수 있다.") + void deleteTopicImage_Success() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(admin)) + .when().delete("/admin/topics/" + topic.getId() + "/images") + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("어드민이 아닐 경우, 특정 토픽 이미지를 삭제할 수 없다.") + void deleteTopicImage_Fail() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(member)) + .when().delete("/admin/topics/" + topic.getId() + "/images") + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + @DisplayName("어드민일 경우, 특정 핀을 삭제할 수 있다.") + void deletePin_Success() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(admin)) + .when().delete("/admin/pins/" + pin.getId()) + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("어드민이 아닐 경우, 특정 핀을 삭제할 수 없다.") + void deletePin_Fail() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(member)) + .when().delete("/admin/pins/" + pin.getId()) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + @DisplayName("어드민일 경우, 특정 핀 이미지를 삭제할 수 있다.") + void deletePinImage_Success() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(admin)) + .when().delete("/admin/pins/images/" + pinImage.getId()) + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()); + } + + @Test + @DisplayName("어드민이 아닐 경우, 특정 핀 이미지를 삭제할 수 없다.") + void deletePinImage_Fail() { + given().log().all() + .header(AUTHORIZATION, testAuthHeaderProvider.createAuthHeader(member)) + .when().delete("/admin/pins/images/" + pinImage.getId()) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + +} From a1b2b54aaaf7f318395322ba3830f096aec1e2a9 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Thu, 14 Sep 2023 20:10:04 +0900 Subject: [PATCH 18/25] =?UTF-8?q?refactor:=20=EC=98=A4=ED=83=88=EC=9E=90?= =?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 --- .../oauth/domain/AuthCodeRequestUrlProviderComposite.java | 2 +- .../mapbefine/oauth/domain/OauthMemberClientComposite.java | 2 +- .../oauth/exception/{OathException.java => OauthException.java} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename backend/src/main/java/com/mapbefine/mapbefine/oauth/exception/{OathException.java => OauthException.java} (94%) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/AuthCodeRequestUrlProviderComposite.java b/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/AuthCodeRequestUrlProviderComposite.java index a2a3c518..2ad2295d 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/AuthCodeRequestUrlProviderComposite.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/AuthCodeRequestUrlProviderComposite.java @@ -4,7 +4,7 @@ import static java.util.function.UnaryOperator.identity; import static java.util.stream.Collectors.toMap; -import com.mapbefine.mapbefine.oauth.exception.OathException.OauthNotFoundException; +import com.mapbefine.mapbefine.oauth.exception.OauthException.OauthNotFoundException; import java.util.Map; import java.util.Optional; import java.util.Set; diff --git a/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/OauthMemberClientComposite.java b/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/OauthMemberClientComposite.java index f5fb9648..eba1172d 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/OauthMemberClientComposite.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/oauth/domain/OauthMemberClientComposite.java @@ -4,7 +4,7 @@ import static java.util.function.UnaryOperator.identity; import static java.util.stream.Collectors.toMap; -import com.mapbefine.mapbefine.oauth.exception.OathException.OauthNotFoundException; +import com.mapbefine.mapbefine.oauth.exception.OauthException.OauthNotFoundException; import java.util.Map; import java.util.Optional; import java.util.Set; diff --git a/backend/src/main/java/com/mapbefine/mapbefine/oauth/exception/OathException.java b/backend/src/main/java/com/mapbefine/mapbefine/oauth/exception/OauthException.java similarity index 94% rename from backend/src/main/java/com/mapbefine/mapbefine/oauth/exception/OathException.java rename to backend/src/main/java/com/mapbefine/mapbefine/oauth/exception/OauthException.java index a18d234c..3dc67e35 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/oauth/exception/OathException.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/oauth/exception/OauthException.java @@ -4,7 +4,7 @@ import com.mapbefine.mapbefine.common.exception.NotFoundException; import com.mapbefine.mapbefine.oauth.domain.OauthServerType; -public class OathException { +public class OauthException { public static class OauthNotFoundException extends NotFoundException { public OauthNotFoundException(OauthErrorCode errorCode, OauthServerType oauthServerType) { From 1e23aee359b10b5fd09c5a699306b09c0db2c96b Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Thu, 14 Sep 2023 20:11:33 +0900 Subject: [PATCH 19/25] =?UTF-8?q?refactor:=20Auth=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=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 --- .../auth/exception/AuthErrorCode.java | 21 +++++++++++++++++ .../auth/exception/AuthException.java | 23 +++++++++++++++++++ .../interceptor/AdminAuthInterceptor.java | 13 ++++------- .../common/interceptor/AuthInterceptor.java | 13 ++++------- .../oauth/application/OauthService.java | 6 ++--- 5 files changed, 56 insertions(+), 20 deletions(-) create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthErrorCode.java create mode 100644 backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthException.java diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthErrorCode.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthErrorCode.java new file mode 100644 index 00000000..7cbf7c20 --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthErrorCode.java @@ -0,0 +1,21 @@ +package com.mapbefine.mapbefine.auth.exception; + +import lombok.Getter; + +@Getter +public enum AuthErrorCode { + ILLEGAL_MEMBER_ID("03100", "로그인에 실패하였습니다."), + ILLEGAL_TOKEN("03101", "로그인에 실패하였습니다."), + FORBIDDEN_ADMIN_ACCESS("03102", "로그인에 실패하였습니다."), + BLOCKING_MEMBER_ACCESS("03103", "로그인에 실패하였습니다."), + ; + + private final String code; + private final String message; + + AuthErrorCode(String code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthException.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthException.java new file mode 100644 index 00000000..4f384f9e --- /dev/null +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/exception/AuthException.java @@ -0,0 +1,23 @@ +package com.mapbefine.mapbefine.auth.exception; + +import com.mapbefine.mapbefine.common.exception.ErrorCode; +import com.mapbefine.mapbefine.common.exception.ForbiddenException; +import com.mapbefine.mapbefine.common.exception.UnauthorizedException; + +public class AuthException { + + public static class AuthUnauthorizedException extends UnauthorizedException { + + public AuthUnauthorizedException(AuthErrorCode errorCode) { + super(new ErrorCode<>(errorCode.getCode(), errorCode.getMessage())); + } + } + + public static class AuthForbiddenException extends ForbiddenException { + + public AuthForbiddenException(AuthErrorCode errorCode) { + super(new ErrorCode<>(errorCode.getCode(), errorCode.getMessage())); + } + } + +} diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java b/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java index 93a0cebe..9f5c8424 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AdminAuthInterceptor.java @@ -2,11 +2,10 @@ import com.mapbefine.mapbefine.auth.application.AuthService; import com.mapbefine.mapbefine.auth.dto.AuthInfo; +import com.mapbefine.mapbefine.auth.exception.AuthErrorCode; +import com.mapbefine.mapbefine.auth.exception.AuthException; import com.mapbefine.mapbefine.auth.infrastructure.AuthorizationExtractor; import com.mapbefine.mapbefine.auth.infrastructure.JwtTokenProvider; -import com.mapbefine.mapbefine.common.exception.ErrorCode; -import com.mapbefine.mapbefine.common.exception.ForbiddenException; -import com.mapbefine.mapbefine.common.exception.UnauthorizedException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Objects; @@ -17,10 +16,6 @@ @Component public class AdminAuthInterceptor implements HandlerInterceptor { - private static final String UNAUTHORIZED_ERROR_MESSAGE = "로그인에 실패하였습니다."; - private static final ErrorCode ILLEGAL_TOKEN = new ErrorCode("03101", UNAUTHORIZED_ERROR_MESSAGE); - private static final ErrorCode FORBIDDEN_ADMIN_ACCESS = new ErrorCode("03102", UNAUTHORIZED_ERROR_MESSAGE); - private final AuthorizationExtractor authorizationExtractor; private final AuthService authService; private final JwtTokenProvider jwtTokenProvider; @@ -62,7 +57,7 @@ private Long extractMemberIdFromToken(HttpServletRequest request) { if (jwtTokenProvider.validateToken(accessToken)) { return Long.parseLong(jwtTokenProvider.getPayload(accessToken)); } - throw new UnauthorizedException(ILLEGAL_TOKEN); + throw new AuthException.AuthUnauthorizedException(AuthErrorCode.ILLEGAL_TOKEN); } private void validateAdmin(Long memberId) { @@ -70,7 +65,7 @@ private void validateAdmin(Long memberId) { return; } - throw new ForbiddenException(FORBIDDEN_ADMIN_ACCESS); + throw new AuthException.AuthForbiddenException(AuthErrorCode.FORBIDDEN_ADMIN_ACCESS); } } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AuthInterceptor.java b/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AuthInterceptor.java index 32a52d38..01c98c73 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AuthInterceptor.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/common/interceptor/AuthInterceptor.java @@ -3,10 +3,11 @@ import com.mapbefine.mapbefine.auth.application.AuthService; import com.mapbefine.mapbefine.auth.domain.AuthMember; import com.mapbefine.mapbefine.auth.dto.AuthInfo; +import com.mapbefine.mapbefine.auth.exception.AuthErrorCode; +import com.mapbefine.mapbefine.auth.exception.AuthException; +import com.mapbefine.mapbefine.auth.exception.AuthException.AuthUnauthorizedException; import com.mapbefine.mapbefine.auth.infrastructure.AuthorizationExtractor; import com.mapbefine.mapbefine.auth.infrastructure.JwtTokenProvider; -import com.mapbefine.mapbefine.common.exception.ErrorCode; -import com.mapbefine.mapbefine.common.exception.UnauthorizedException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Arrays; @@ -18,10 +19,6 @@ @Component public class AuthInterceptor implements HandlerInterceptor { - private static final String UNAUTHORIZED_ERROR_MESSAGE = "로그인에 실패하였습니다."; - private static final ErrorCode ILLEGAL_MEMBER_ID = new ErrorCode("03100", UNAUTHORIZED_ERROR_MESSAGE); - private static final ErrorCode ILLEGAL_TOKEN = new ErrorCode("03101", UNAUTHORIZED_ERROR_MESSAGE); - private final AuthorizationExtractor authorizationExtractor; private final AuthService authService; private final JwtTokenProvider jwtTokenProvider; @@ -65,7 +62,7 @@ private void validateMember(Long memberId) { return; } - throw new UnauthorizedException(ILLEGAL_MEMBER_ID); + throw new AuthUnauthorizedException(AuthErrorCode.ILLEGAL_MEMBER_ID); } private boolean isAuthMemberNotRequired(HandlerMethod handlerMethod) { @@ -86,7 +83,7 @@ private Long extractMemberIdFromToken(HttpServletRequest request) { } String accessToken = authInfo.accessToken(); if (!jwtTokenProvider.validateToken(accessToken)) { - throw new UnauthorizedException(ILLEGAL_TOKEN); + throw new AuthException.AuthUnauthorizedException(AuthErrorCode.ILLEGAL_TOKEN); } return Long.parseLong(jwtTokenProvider.getPayload(accessToken)); } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java b/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java index f951590b..50681719 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/oauth/application/OauthService.java @@ -1,10 +1,10 @@ package com.mapbefine.mapbefine.oauth.application; +import com.mapbefine.mapbefine.auth.exception.AuthErrorCode; +import com.mapbefine.mapbefine.auth.exception.AuthException.AuthUnauthorizedException; import com.mapbefine.mapbefine.auth.infrastructure.JwtTokenProvider; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; -import com.mapbefine.mapbefine.member.exception.MemberErrorCode; -import com.mapbefine.mapbefine.member.exception.MemberException.MemberForbiddenException; import com.mapbefine.mapbefine.oauth.domain.AuthCodeRequestUrlProviderComposite; import com.mapbefine.mapbefine.oauth.domain.OauthMember; import com.mapbefine.mapbefine.oauth.domain.OauthMemberClientComposite; @@ -58,7 +58,7 @@ private void validateMemberStatus(Member member) { return; } - throw new MemberForbiddenException(MemberErrorCode.FORBIDDEN_MEMBER_STATUS, member.getId()); + throw new AuthUnauthorizedException(AuthErrorCode.BLOCKING_MEMBER_ACCESS); } } From 605a2e3a1728abcb57426ced0c79645b370788a9 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Thu, 14 Sep 2023 20:33:15 +0900 Subject: [PATCH 20/25] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapbefine/auth/application/AuthService.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java index ce7c3a20..11b2e343 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/application/AuthService.java @@ -5,11 +5,10 @@ import com.mapbefine.mapbefine.auth.domain.member.User; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; -import com.mapbefine.mapbefine.member.exception.MemberErrorCode; -import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; import com.mapbefine.mapbefine.topic.domain.Topic; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -67,13 +66,10 @@ public boolean isAdmin(Long memberId) { return false; } - Member member = findMember(memberId); + Optional member = memberRepository.findById(memberId); - return member.isAdmin(); + return member.map(Member::isAdmin) + .orElse(false); } - private Member findMember(Long memberId) { - return memberRepository.findById(memberId) - .orElseThrow(() -> new MemberNotFoundException(MemberErrorCode.MEMBER_NOT_FOUND, memberId)); - } } From e5a898de5829c94aeb838b14220440ac82c18432 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Thu, 14 Sep 2023 21:21:52 +0900 Subject: [PATCH 21/25] =?UTF-8?q?refactor:=20findMemberById=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapbefine/admin/application/AdminCommandService.java | 5 ++--- .../mapbefine/admin/application/AdminQueryService.java | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index e03f19a3..a9117dc6 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -7,8 +7,6 @@ import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; import com.mapbefine.mapbefine.member.domain.Status; -import com.mapbefine.mapbefine.member.exception.MemberErrorCode; -import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; import com.mapbefine.mapbefine.pin.domain.Pin; import com.mapbefine.mapbefine.pin.domain.PinImageRepository; @@ -17,6 +15,7 @@ import com.mapbefine.mapbefine.topic.domain.TopicRepository; import com.mapbefine.mapbefine.topic.exception.TopicException; import java.util.List; +import java.util.NoSuchElementException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -57,7 +56,7 @@ public void blockMember(AuthMember authMember, Long memberId) { private Member findMemberById(Long id) { return memberRepository.findById(id) - .orElseThrow(() -> new MemberNotFoundException(MemberErrorCode.MEMBER_NOT_FOUND, id)); + .orElseThrow(() -> new NoSuchElementException("findMemberByAuthMember; member not found; id=" + id)); } private void validateAdminPermission(Member member) { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java index e2b4520a..3bfa4474 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -6,13 +6,12 @@ import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; import com.mapbefine.mapbefine.member.domain.Role; -import com.mapbefine.mapbefine.member.exception.MemberErrorCode; -import com.mapbefine.mapbefine.member.exception.MemberException.MemberNotFoundException; import com.mapbefine.mapbefine.permission.exception.PermissionErrorCode; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; import com.mapbefine.mapbefine.pin.domain.Pin; import com.mapbefine.mapbefine.topic.domain.Topic; import java.util.List; +import java.util.NoSuchElementException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -39,7 +38,7 @@ public List findAllMemberDetails(AuthMember authMember) { private Member findMemberById(Long id) { return memberRepository.findById(id) - .orElseThrow(() -> new MemberNotFoundException(MemberErrorCode.MEMBER_NOT_FOUND, id)); + .orElseThrow(() -> new NoSuchElementException("findMemberByAuthMember; member not found; id=" + id)); } private void validateAdminPermission(Member member) { From 53f9c421b7e9875de6952b8e98a8590a53b7208b Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Thu, 14 Sep 2023 22:47:25 +0900 Subject: [PATCH 22/25] =?UTF-8?q?test:=20GithubActions=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java b/backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java index 419c68e9..c7d7c26f 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/admin/AdminIntegrationTest.java @@ -131,6 +131,7 @@ void findMemberDetail_Success() { assertThat(response).usingRecursiveComparison() .ignoringFields("updatedAt") + .ignoringFields("topics.updatedAt") .isEqualTo(expected); } From f7afa504bf256946b7bc0e83707f04890fdd40bb Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Fri, 15 Sep 2023 14:13:46 +0900 Subject: [PATCH 23/25] =?UTF-8?q?refactor:=20isAdmin()=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 25 ++++++------------- .../admin/application/AdminQueryService.java | 12 ++++----- .../mapbefine/auth/domain/AuthMember.java | 3 +++ .../mapbefine/auth/domain/member/Admin.java | 6 +++++ .../mapbefine/auth/domain/member/Guest.java | 5 ++++ .../mapbefine/auth/domain/member/User.java | 6 +++++ .../mapbefine/member/MemberFixture.java | 5 ++++ 7 files changed, 38 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index a9117dc6..77756174 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -6,6 +6,7 @@ import com.mapbefine.mapbefine.auth.domain.AuthMember; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; +import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.member.domain.Status; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; import com.mapbefine.mapbefine.pin.domain.Pin; @@ -41,9 +42,7 @@ public AdminCommandService( } public void blockMember(AuthMember authMember, Long memberId) { - Member admin = findMemberById(authMember.getMemberId()); - - validateAdminPermission(admin); + validateAdminPermission(authMember); Member member = findMemberById(memberId); member.updateStatus(Status.BLOCKED); @@ -59,8 +58,8 @@ private Member findMemberById(Long id) { .orElseThrow(() -> new NoSuchElementException("findMemberByAuthMember; member not found; id=" + id)); } - private void validateAdminPermission(Member member) { - if (member.isAdmin()) { + private void validateAdminPermission(AuthMember authMember) { + if (authMember.isRole(Role.ADMIN)) { return; } @@ -75,17 +74,13 @@ private List extractPinIdsByMember(Member member) { } public void deleteTopic(AuthMember authMember, Long topicId) { - Member member = findMemberById(authMember.getMemberId()); - - validateAdminPermission(member); + validateAdminPermission(authMember); topicRepository.deleteById(topicId); } public void deleteTopicImage(AuthMember authMember, Long topicId) { - Member member = findMemberById(authMember.getMemberId()); - - validateAdminPermission(member); + validateAdminPermission(authMember); Topic topic = findTopicById(topicId); topic.removeImage(); @@ -97,17 +92,13 @@ private Topic findTopicById(Long topicId) { } public void deletePin(AuthMember authMember, Long pinId) { - Member member = findMemberById(authMember.getMemberId()); - - validateAdminPermission(member); + validateAdminPermission(authMember); pinRepository.deleteById(pinId); } public void deletePinImage(AuthMember authMember, Long pinImageId) { - Member member = findMemberById(authMember.getMemberId()); - - validateAdminPermission(member); + validateAdminPermission(authMember); pinImageRepository.deleteById(pinImageId); } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java index 3bfa4474..f7ca346a 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminQueryService.java @@ -26,8 +26,7 @@ public AdminQueryService(MemberRepository memberRepository) { } public List findAllMemberDetails(AuthMember authMember) { - Member admin = findMemberById(authMember.getMemberId()); - validateAdminPermission(admin); + validateAdminPermission(authMember); List members = memberRepository.findAllByMemberInfoRole(Role.USER); @@ -41,8 +40,8 @@ private Member findMemberById(Long id) { .orElseThrow(() -> new NoSuchElementException("findMemberByAuthMember; member not found; id=" + id)); } - private void validateAdminPermission(Member member) { - if (member.isAdmin()) { + private void validateAdminPermission(AuthMember authMember) { + if (authMember.isRole(Role.ADMIN)) { return; } @@ -50,9 +49,7 @@ private void validateAdminPermission(Member member) { } public AdminMemberDetailResponse findMemberDetail(AuthMember authMember, Long memberId) { - Member admin = findMemberById(authMember.getMemberId()); - - validateAdminPermission(admin); + validateAdminPermission(authMember); Member findMember = findMemberById(memberId); List topics = findMember.getCreatedTopics(); @@ -60,4 +57,5 @@ public AdminMemberDetailResponse findMemberDetail(AuthMember authMember, Long me return AdminMemberDetailResponse.of(findMember, topics, pins); } + } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/AuthMember.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/AuthMember.java index c795b1ec..d27f7cc2 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/AuthMember.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/AuthMember.java @@ -1,5 +1,6 @@ package com.mapbefine.mapbefine.auth.domain; +import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.topic.domain.Topic; import java.util.List; @@ -27,6 +28,8 @@ protected AuthMember( public abstract boolean canPinCreateOrUpdate(Topic topic); + public abstract boolean isRole(Role role); + public Long getMemberId() { return memberId; } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Admin.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Admin.java index 3d97522c..f6a54648 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Admin.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Admin.java @@ -1,6 +1,7 @@ package com.mapbefine.mapbefine.auth.domain.member; import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.topic.domain.Topic; import java.util.Collections; @@ -34,4 +35,9 @@ public boolean canPinCreateOrUpdate(Topic topic) { return true; } + @Override + public boolean isRole(Role role) { + return Role.ADMIN == role; + } + } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Guest.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Guest.java index 6c8c0edc..dcd8dbeb 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Guest.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/Guest.java @@ -1,6 +1,7 @@ package com.mapbefine.mapbefine.auth.domain.member; import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.topic.domain.Topic; import com.mapbefine.mapbefine.topic.domain.TopicStatus; import java.util.Collections; @@ -36,4 +37,8 @@ public boolean canPinCreateOrUpdate(Topic topic) { return false; } + @Override + public boolean isRole(Role role) { + return Role.GUEST == role; + } } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/User.java b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/User.java index d2444d49..ed2ea7b8 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/User.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/auth/domain/member/User.java @@ -1,6 +1,7 @@ package com.mapbefine.mapbefine.auth.domain.member; import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.topic.domain.Topic; import com.mapbefine.mapbefine.topic.domain.TopicStatus; import java.util.List; @@ -54,4 +55,9 @@ private boolean hasPermission(Long topicId) { return createdTopic.contains(topicId) || topicsWithPermission.contains(topicId); } + @Override + public boolean isRole(Role role) { + return Role.USER == role; + } + } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java b/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java index e31c50d1..6fd7bc14 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/member/MemberFixture.java @@ -1,6 +1,7 @@ package com.mapbefine.mapbefine.member; import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.auth.domain.member.Admin; import com.mapbefine.mapbefine.auth.domain.member.User; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.OauthId; @@ -34,6 +35,10 @@ public static Member createWithOauthId(String name, String email, Role role, Oau } public static AuthMember createUser(Member member) { + if (member.isAdmin()) { + return new Admin(member.getId()); + } + return new User( member.getId(), member.getCreatedTopics().stream().map(Topic::getId).toList(), From 9084afd18953d2b705152d832db81b45fca791f1 Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Fri, 15 Sep 2023 14:28:34 +0900 Subject: [PATCH 24/25] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C(=ED=83=88=ED=87=B4)=EC=8B=9C,=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=A0=95=EB=B3=B4(=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=20=EB=93=B1)=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AdminCommandService.java | 25 ++++++++++++++-- .../atlas/domain/AtlasRepository.java | 1 + .../domain/PermissionRepository.java | 1 + .../application/AdminCommandServiceTest.java | 29 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java index 77756174..0dfaf518 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/admin/application/AdminCommandService.java @@ -3,11 +3,14 @@ import static com.mapbefine.mapbefine.permission.exception.PermissionErrorCode.PERMISSION_FORBIDDEN_BY_NOT_ADMIN; import static com.mapbefine.mapbefine.topic.exception.TopicErrorCode.TOPIC_NOT_FOUND; +import com.mapbefine.mapbefine.atlas.domain.AtlasRepository; import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.bookmark.domain.BookmarkRepository; import com.mapbefine.mapbefine.member.domain.Member; import com.mapbefine.mapbefine.member.domain.MemberRepository; import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.member.domain.Status; +import com.mapbefine.mapbefine.permission.domain.PermissionRepository; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; import com.mapbefine.mapbefine.pin.domain.Pin; import com.mapbefine.mapbefine.pin.domain.PinImageRepository; @@ -28,17 +31,26 @@ public class AdminCommandService { private final TopicRepository topicRepository; private final PinRepository pinRepository; private final PinImageRepository pinImageRepository; + private final PermissionRepository permissionRepository; + private final AtlasRepository atlasRepository; + private final BookmarkRepository bookmarkRepository; public AdminCommandService( MemberRepository memberRepository, TopicRepository topicRepository, PinRepository pinRepository, - PinImageRepository pinImageRepository + PinImageRepository pinImageRepository, + PermissionRepository permissionRepository, + AtlasRepository atlasRepository, + BookmarkRepository bookmarkRepository ) { this.memberRepository = memberRepository; this.topicRepository = topicRepository; this.pinRepository = pinRepository; this.pinImageRepository = pinImageRepository; + this.permissionRepository = permissionRepository; + this.atlasRepository = atlasRepository; + this.bookmarkRepository = bookmarkRepository; } public void blockMember(AuthMember authMember, Long memberId) { @@ -46,11 +58,20 @@ public void blockMember(AuthMember authMember, Long memberId) { Member member = findMemberById(memberId); member.updateStatus(Status.BLOCKED); + + deleteAllRelatedMember(member); + } + + private void deleteAllRelatedMember(Member member) { List pinIds = extractPinIdsByMember(member); + Long memberId = member.getId(); + pinImageRepository.deleteAllByPinIds(pinIds); topicRepository.deleteAllByMemberId(memberId); pinRepository.deleteAllByMemberId(memberId); - pinImageRepository.deleteAllByPinIds(pinIds); + permissionRepository.deleteAllByMemberId(memberId); + atlasRepository.deleteAllByMemberId(memberId); + bookmarkRepository.deleteAllByMemberId(memberId); } private Member findMemberById(Long id) { diff --git a/backend/src/main/java/com/mapbefine/mapbefine/atlas/domain/AtlasRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/atlas/domain/AtlasRepository.java index 242d6992..bd37a9ba 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/atlas/domain/AtlasRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/atlas/domain/AtlasRepository.java @@ -10,4 +10,5 @@ public interface AtlasRepository extends JpaRepository { void deleteByMemberIdAndTopicId(Long memberId, Long topicId); + void deleteAllByMemberId(Long memberId); } diff --git a/backend/src/main/java/com/mapbefine/mapbefine/permission/domain/PermissionRepository.java b/backend/src/main/java/com/mapbefine/mapbefine/permission/domain/PermissionRepository.java index da8dca9b..7d14c061 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/permission/domain/PermissionRepository.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/permission/domain/PermissionRepository.java @@ -9,4 +9,5 @@ public interface PermissionRepository extends JpaRepository { boolean existsByTopicIdAndMemberId(Long topicId, Long memberId); + void deleteAllByMemberId(Long memberId); } diff --git a/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java b/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java index e490ab2d..12e49f77 100644 --- a/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java +++ b/backend/src/test/java/com/mapbefine/mapbefine/admin/application/AdminCommandServiceTest.java @@ -4,7 +4,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import com.mapbefine.mapbefine.atlas.domain.Atlas; +import com.mapbefine.mapbefine.atlas.domain.AtlasRepository; import com.mapbefine.mapbefine.auth.domain.AuthMember; +import com.mapbefine.mapbefine.bookmark.domain.Bookmark; +import com.mapbefine.mapbefine.bookmark.domain.BookmarkRepository; import com.mapbefine.mapbefine.common.annotation.ServiceTest; import com.mapbefine.mapbefine.location.LocationFixture; import com.mapbefine.mapbefine.location.domain.Location; @@ -14,6 +18,8 @@ import com.mapbefine.mapbefine.member.domain.MemberRepository; import com.mapbefine.mapbefine.member.domain.Role; import com.mapbefine.mapbefine.member.domain.Status; +import com.mapbefine.mapbefine.permission.domain.Permission; +import com.mapbefine.mapbefine.permission.domain.PermissionRepository; import com.mapbefine.mapbefine.permission.exception.PermissionException.PermissionForbiddenException; import com.mapbefine.mapbefine.pin.PinFixture; import com.mapbefine.mapbefine.pin.PinImageFixture; @@ -50,6 +56,16 @@ class AdminCommandServiceTest { @Autowired private PinImageRepository pinImageRepository; + + @Autowired + private AtlasRepository atlasRepository; + + @Autowired + private PermissionRepository permissionRepository; + + @Autowired + private BookmarkRepository bookmarkRepository; + private Location location; private Member admin; private Member member; @@ -72,12 +88,22 @@ void setup() { void blockMember_Success() { //given AuthMember adminAuthMember = MemberFixture.createUser(admin); + Bookmark bookmark = Bookmark.createWithAssociatedTopicAndMember(topic, member); + Atlas atlas = Atlas.createWithAssociatedMember(topic, member); + Permission permission = Permission.createPermissionAssociatedWithTopicAndMember(topic, member); + + bookmarkRepository.save(bookmark); + atlasRepository.save(atlas); + permissionRepository.save(permission); assertAll(() -> { assertThat(member.getMemberInfo().getStatus()).isEqualTo(Status.NORMAL); assertThat(topic.isDeleted()).isFalse(); assertThat(pin.isDeleted()).isFalse(); assertThat(pinImage.isDeleted()).isFalse(); + assertThat(bookmarkRepository.existsByMemberIdAndTopicId(member.getId(), topic.getId())).isTrue(); + assertThat(atlasRepository.existsByMemberIdAndTopicId(member.getId(), topic.getId())).isTrue(); + assertThat(permissionRepository.existsByTopicIdAndMemberId(topic.getId(), member.getId())).isTrue(); }); //when @@ -93,6 +119,9 @@ void blockMember_Success() { assertThat(deletedTopic.isDeleted()).isTrue(); assertThat(deletedPin.isDeleted()).isTrue(); assertThat(deletedPinImage.isDeleted()).isTrue(); + assertThat(bookmarkRepository.existsByMemberIdAndTopicId(member.getId(), topic.getId())).isFalse(); + assertThat(atlasRepository.existsByMemberIdAndTopicId(member.getId(), topic.getId())).isFalse(); + assertThat(permissionRepository.existsByTopicIdAndMemberId(topic.getId(), member.getId())).isFalse(); }); } From b30c8216ff179cb9ebc9f32397f75ada573744ec Mon Sep 17 00:00:00 2001 From: cpot5620 Date: Fri, 15 Sep 2023 14:52:13 +0900 Subject: [PATCH 25/25] =?UTF-8?q?refactor:=20Member=20status=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EA=B0=92=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/mapbefine/mapbefine/member/domain/MemberInfo.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java index 08276157..3cc5dfa4 100644 --- a/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java +++ b/backend/src/main/java/com/mapbefine/mapbefine/member/domain/MemberInfo.java @@ -16,6 +16,7 @@ import java.util.Objects; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; @Embeddable @NoArgsConstructor(access = PROTECTED) @@ -39,6 +40,7 @@ public class MemberInfo { private Role role; @Enumerated(EnumType.STRING) + @ColumnDefault(value = "NORMAL") @Column(nullable = false) private Status status;