From 6f210f5acf4e51ff8124a3bd8efdf991422f1ba2 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 00:06:28 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupCapsuleQueryRepository.java | 10 +++ .../core/domain/group/api/GroupApi.java | 44 +++++++++--- .../domain/group/api/GroupApiController.java | 10 ++- .../core/domain/group/entity/GroupInvite.java | 13 ++-- .../exception/GroupDeleteFailException.java | 11 +++ .../GroupInviteQueryRepository.java | 2 + .../GroupInviteQueryRepositoryImpl.java | 15 ++++ .../GroupInviteRepository.java | 8 +++ .../groupRepository/GroupRepository.java | 1 + .../MemberGroupRepository.java | 5 ++ .../group/service/GroupServiceImpl.java | 5 ++ .../service/write/GroupWriteService.java | 1 + .../service/write/GroupWriteServiceImpl.java | 69 ++++++++++++++++++- .../core/global/error/ErrorCode.java | 3 + .../core/infra/s3/config/S3Config.java | 9 +++ .../infra/s3/manager/S3ObjectManager.java | 43 ++++++++++++ 16 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupDeleteFailException.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3ObjectManager.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepository.java index 047d86fff..b305e80ac 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepository.java @@ -166,4 +166,14 @@ public Slice findMyGroupCapsuleSlice( return new SliceImpl<>(groupCapsules, Pageable.ofSize(size), hasNext); } + + public boolean findGroupCapsuleExistByGroupId(Long groupId) { + Integer count = jpaQueryFactory + .selectOne() + .from(capsule) + .where(capsule.group.id.eq(groupId)) + .fetchFirst(); + + return count != null; + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java index 7f79e682a..ad2866260 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -18,6 +19,7 @@ import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; import site.timecapsulearchive.core.domain.group.data.response.GroupsSliceResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; +import site.timecapsulearchive.core.global.error.ErrorResponse; public interface GroupApi { @@ -74,20 +76,46 @@ ResponseEntity> createGroup( @Operation( summary = "그룹 삭제", - description = "그룹장인 경우에 특정 그룹을 삭제한다.", + description = """ + 그룹 삭제를 요청한 사용자가 해당 그룹의 그룹장인 경우 그룹을 삭제한다.
+ 주의 +
    +
  • 그룹에 포함된 멤버가 그룹장을 제외하고 존재하면 그룹을 삭제할 수 없다.
  • +
  • 그룹장이 아닌 경우 그룹을 삭제할 수 없다.
  • +
  • 그룹에 그룹 캡슐이 남아있는 경우 삭제할 수 없다.
  • +
+ """, security = {@SecurityRequirement(name = "user_token")}, tags = {"group"} ) @ApiResponses(value = { @ApiResponse( - responseCode = "204", - description = "처리완료" - ) + responseCode = "202", + description = "처리 시작" + ), + @ApiResponse( + responseCode = "400", + description = """ + 다음의 경우 예외가 발생한다. +
    +
  • 삭제를 요청한 그룹에 그룹장을 제외한 그룹원이 존재하는 경우
  • +
  • 삭제를 요청한 그룹에서 요청한 사용자가 그룹장이 아닌 경우
  • +
  • 삭제를 요청한 그룹에 그룹 캡슐이 존재하는 경우
  • +
+ """, + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "그룹이 존재하지 않으면 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), }) - @DeleteMapping(value = "/groups/{group_id}") - ResponseEntity deleteGroupById( - @Parameter(in = ParameterIn.PATH, description = "수정할 그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId + ResponseEntity> deleteGroupById( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "삭제할 그룹 아이디", required = true) + Long groupId ); @Operation( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java index 3efb49539..bf7e50c5d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java @@ -72,9 +72,15 @@ public ResponseEntity> createGroup( ); } + @DeleteMapping(value = "/{group_id}") @Override - public ResponseEntity deleteGroupById(Long groupId) { - return null; + public ResponseEntity> deleteGroupById( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId + ) { + groupService.deleteGroup(memberId, groupId); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } @Override diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java index 690014c01..7ff563148 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java @@ -26,8 +26,9 @@ public class GroupInvite extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "group_id", nullable = false) - private Long groupId; + @ManyToOne + @JoinColumn(name = "group_id", nullable = false) + private Group group; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "group_owner_id", nullable = false) @@ -37,14 +38,14 @@ public class GroupInvite extends BaseEntity { @JoinColumn(name = "group_member_id", nullable = false) private Member groupMember; - private GroupInvite(Long groupId, Member groupOwner, Member groupMember) { - this.groupId = groupId; + private GroupInvite(Group group, Member groupOwner, Member groupMember) { + this.group = group; this.groupOwner = groupOwner; this.groupMember = groupMember; } - public static GroupInvite createOf(Long groupId, Member groupOwner, Member groupMember) { - return new GroupInvite(groupId, groupOwner, groupMember); + public static GroupInvite createOf(Group group, Member groupOwner, Member groupMember) { + return new GroupInvite(group, groupOwner, groupMember); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupDeleteFailException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupDeleteFailException.java new file mode 100644 index 000000000..87fab448d --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupDeleteFailException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.group.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class GroupDeleteFailException extends BusinessException { + + public GroupDeleteFailException(ErrorCode errorCode) { + super(errorCode); + } +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java index 757acc930..279dd9d5e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -5,4 +5,6 @@ public interface GroupInviteQueryRepository { void bulkSave(final Long groupOwnerId, final List groupMemberIds); + + List findGroupInviteIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 8d71332c7..6f6c78ec2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -1,5 +1,8 @@ package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; +import static site.timecapsulearchive.core.domain.group.entity.QGroupInvite.groupInvite; + +import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; @@ -16,6 +19,7 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepository { private final JdbcTemplate jdbcTemplate; + private final JPAQueryFactory jpaQueryFactory; @Override public void bulkSave(Long groupOwnerId, List groupMemberIds) { @@ -45,4 +49,15 @@ public int getBatchSize() { ); } + @Override + public List findGroupInviteIdsByGroupIdAndGroupOwnerId( + final Long groupId, + final Long memberId + ) { + return jpaQueryFactory + .select(groupInvite.id) + .from(groupInvite) + .where(groupInvite.group.id.eq(groupId).and(groupInvite.groupOwner.id.eq(memberId))) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java index 1331805dd..8b876cb9a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java @@ -1,6 +1,10 @@ package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; +import java.util.List; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; import site.timecapsulearchive.core.domain.group.entity.GroupInvite; public interface GroupInviteRepository extends Repository, @@ -10,5 +14,9 @@ public interface GroupInviteRepository extends Repository, int deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId(Long groupId, Long groupOwnerId, Long groupMemberId); + + @Query("delete from GroupInvite gi where gi.id in :groupInviteIds") + @Modifying(clearAutomatically = true) + void bulkDelete(@Param("groupInviteIds") List groupInviteIds); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java index 24eb4c0c7..ac60cac3c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java @@ -10,4 +10,5 @@ public interface GroupRepository extends Repository, GroupQueryRepo Optional findGroupById(Long groupId); + void delete(Group group); } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java index d5ded67da..fdd73b90d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.group.repository.memberGroupRepository; +import java.util.List; import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.group.entity.MemberGroup; @@ -7,4 +8,8 @@ public interface MemberGroupRepository extends Repository, MemberGroupQueryRepository { void save(MemberGroup memberGroup); + + List findMemberGroupsByGroupId(Long groupId); + + void delete(MemberGroup memberGroup); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java index b1252d0ea..c4a66d617 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java @@ -57,4 +57,9 @@ public void rejectRequestGroup(final Long groupMemberId, final Long groupId, public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { groupWriteService.acceptGroupInvite(memberId, groupId, targetId); } + + @Override + public void deleteGroup(final Long memberId, final Long groupId) { + groupWriteService.deleteGroup(memberId, groupId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java index 6eb08e6f6..d7eb52fcb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java @@ -12,4 +12,5 @@ public interface GroupWriteService { void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId); + void deleteGroup(final Long memberId, final Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java index b2065bee5..0c5e0a92b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java @@ -7,11 +7,13 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.entity.GroupInvite; import site.timecapsulearchive.core.domain.group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; import site.timecapsulearchive.core.domain.group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupOwnerAuthenticateException; @@ -21,7 +23,9 @@ import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; +import site.timecapsulearchive.core.infra.s3.manager.S3ObjectManager; @Service @RequiredArgsConstructor @@ -33,6 +37,8 @@ public class GroupWriteServiceImpl implements GroupWriteService { private final GroupInviteRepository groupInviteRepository; private final TransactionTemplate transactionTemplate; private final SocialNotificationManager socialNotificationManager; + private final GroupCapsuleQueryRepository groupCapsuleQueryRepository; + private final S3ObjectManager s3ObjectManager; public void createGroup(final Long memberId, final GroupCreateDto dto) { final Member member = memberRepository.findMemberById(memberId) @@ -62,7 +68,10 @@ public void inviteGroup(final Long memberId, final Long groupId, final Long targ final Member groupMember = memberRepository.findMemberById(targetId).orElseThrow( MemberNotFoundException::new); - final GroupInvite groupInvite = GroupInvite.createOf(groupId, groupOwner, groupMember); + final Group group = groupRepository.findGroupById(groupId) + .orElseThrow(GroupNotFoundException::new); + + final GroupInvite groupInvite = GroupInvite.createOf(group, groupOwner, groupMember); final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; @@ -117,5 +126,63 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), targetId); } + /** + * 사용자가 그룹장인 그룹을 삭제한다. + *
주의 - 그룹 삭제 시 아래 조건에 해당하면 예외가 발생한다. + *
1. 그룹이 존재하지 않는 경우 + *
2. 그룹에 멤버가 존재하는 경우 + *
3. 그룹 삭제를 요청한 사용자가 그룹장이 아닌 경우 + *
4. 그룹에 그룹 캡슐이 남아있는 경우 + * + * @param memberId 그룹에 속한 그룹장 아이디 + * @param groupId 그룹 아이디 + */ + public void deleteGroup(final Long memberId, final Long groupId) { + final String[] groupProfilePath = new String[1]; + + transactionTemplate.executeWithoutResult(transactionStatus -> { + final Group group = groupRepository.findGroupById(groupId) + .orElseThrow(GroupNotFoundException::new); + + final List groupMembers = memberGroupRepository.findMemberGroupsByGroupId( + groupId); + + checkGroupOwnership(memberId, groupMembers); + checkGroupMemberExist(groupMembers); + checkGroupCapsuleExist(groupId); + + List groupInviteIds = groupInviteRepository.findGroupInviteIdsByGroupIdAndGroupOwnerId( + groupId, memberId); + groupInviteRepository.bulkDelete(groupInviteIds); + + groupRepository.delete(group); + groupProfilePath[0] = group.getGroupProfileUrl(); + }); + + s3ObjectManager.deleteObject(groupProfilePath[0]); + } + + private void checkGroupOwnership(Long memberId, List groupMembers) { + final boolean isGroupOwner = groupMembers.stream() + .anyMatch(mg -> mg.getMember().getId().equals(memberId) && mg.getIsOwner()); + if (!isGroupOwner) { + throw new GroupDeleteFailException(ErrorCode.NO_GROUP_AUTHORITY_ERROR); + } + } + private void checkGroupMemberExist(List groupMembers) { + final boolean groupMemberExist = groupMembers.size() > 1; + if (groupMemberExist) { + throw new GroupDeleteFailException(ErrorCode.GROUP_MEMBER_EXIST_ERROR); + } + groupMembers.forEach(memberGroupRepository::delete); + } + + private void checkGroupCapsuleExist(Long groupId) { + final boolean groupCapsuleExist = groupCapsuleQueryRepository.findGroupCapsuleExistByGroupId( + groupId); + if (groupCapsuleExist) { + throw new GroupDeleteFailException(ErrorCode.GROUP_CAPSULE_EXIST_ERROR); + } + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java index 434fbf85e..2ea8d6fe7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java @@ -62,6 +62,9 @@ public enum ErrorCode { GROUP_NOT_FOUND_ERROR(404, "GROUP-002", "그룹을 찾을 수 없습니다"), GROUP_OWNER_AUTHENTICATE_ERROR(400, "GROUP-003", "그룹장이 아닙니다."), GROUP_INVITATION_NOT_FOUND_ERROR(404, "GROUP-004", "그룹 초대를 찾을 수 없습니다"), + GROUP_MEMBER_EXIST_ERROR(400, "GROUP-005", "다른 그룹 멤버가 존재해 그룹을 삭제할 수 없습니다."), + NO_GROUP_AUTHORITY_ERROR(403, "GROUP-006", "그룹에 대한 권한이 존재하지 않습니다."), + GROUP_CAPSULE_EXIST_ERROR(400, "GROUP-007", "그룹 캡슐이 존재해 그룹을 삭제할 수 없습니다."), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/config/S3Config.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/config/S3Config.java index 5d18172e2..8378fdee0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/config/S3Config.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/config/S3Config.java @@ -6,6 +6,7 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.presigner.S3Presigner; @Configuration @@ -23,6 +24,14 @@ public S3Presigner s3Presigner() { .build(); } + @Bean + public S3Client s3Client() { + return S3Client.builder() + .credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials())) + .region(Region.of(s3Properties.region())) + .build(); + } + private AwsBasicCredentials awsBasicCredentials() { return AwsBasicCredentials.create(s3Properties.accessKey(), s3Properties.secretKey()); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3ObjectManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3ObjectManager.java new file mode 100644 index 000000000..119f60b74 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3ObjectManager.java @@ -0,0 +1,43 @@ +package site.timecapsulearchive.core.infra.s3.manager; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import site.timecapsulearchive.core.infra.s3.config.S3Config; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; + +@Slf4j +@Component +public class S3ObjectManager { + + private final S3Client s3Client; + private final String bucketName; + + public S3ObjectManager(S3Client s3Client, S3Config s3config) { + this.s3Client = s3Client; + this.bucketName = s3config.getBucketName(); + } + + /** + * s3 상의 경로를 입력한다. (버킷명 제외) + *
ex) capsule/1/test.jpg + * + * @param path s3 상에서 경로 + */ + public void deleteObject(String path) { + try { + s3Client.deleteObject( + DeleteObjectRequest.builder() + .bucket(bucketName) + .key(path) + .build() + ); + } catch (AwsServiceException e) { + log.error("Aws S3에서 삭제 요청 처리 실패 - {}", path, e); + } catch (SdkClientException e) { + log.error("Aws S3 삭제 요청 클라이언트 처리 실패 - {}", path, e); + } + } +} \ No newline at end of file From 6a9ad67b634fd8ae8732fb23429a8dc98ddcedbb Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 00:06:38 +0900 Subject: [PATCH 2/8] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/fixture/domain/MemberFixture.java | 44 +++++- .../fixture/domain/MemberGroupFixture.java | 2 +- .../group/service/GroupWriteServiceTest.java | 144 +++++++++++++++++- 3 files changed, 187 insertions(+), 3 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java index 9126a5f9a..ccac4f31c 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.common.fixture.domain; +import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -14,9 +15,33 @@ public class MemberFixture { private static final HashEncryptionManager hashEncryptionManager = UnitTestDependency.hashEncryptionManager(); + /** + * 테스트 픽스처 - {@code Member}의 {@code id}가 주어진 {@code memberId}로 설정된 멤버 엔티티를 생성한다. + * + * @param memberId 설정할 멤버 아이디 + * @return {@code Member}의 {@code id}가 {@code memberId}로 설정된 {@code Member} 테스트 픽스처 + */ + public static Member memberWithMemberId(long memberId) { + try { + Member member = member((int) memberId); + setFieldValue(member, "id", memberId); + return member; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void setFieldValue(Object instance, String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(instance, value); + } + /** * 테스트 픽스처 - 멤버 마다 상이한 값을 위한 dataPrefix를 주면 멤버를 생성한다. *
주의 - 테스트에서 같은 prefix를 사용하면 오류가 발생하므로 서로 다른 prefix를 쓰도록 해야함. + * * @param dataPrefix prefix * @return {@code Member} 테스트 픽스처 */ @@ -56,8 +81,9 @@ public static List getPhones(int count) { /** * 테스트 픽스처 - 크기와 멤버 마다 상이한 값을 위한 startDataPrefix를 주면 멤버들을 생성한다. *
주의 - 테스트에서 같은 prefix를 사용하면 오류가 발생하므로 서로 다른 prefix를 쓰도록 해야함. + * * @param startDataPrefix 시작 prefix - * @param count 크기 + * @param count 크기 * @return {@code List} 테스트 픽스처들 */ public static List members(int startDataPrefix, int count) { @@ -77,4 +103,20 @@ public static List getPhoneBytesList(int start, int count) { return result; } + + /** + * 테스트 픽스처 - 리스트의 {@code Member}의 {@code id}가 주어진 {@code memberId}로 설정된 멤버 엔티티 리스트를 생성한다. + * + * @param startDataPrefix 시작 id + * @param count 크기 + * @return 리스트의 각 {@code Member}의 {@code id}가 {@code memberId}로 설정된 {@code List} 테스트 픽스처 + */ + public static List membersWithMemberId(int startDataPrefix, int count) { + List result = new ArrayList<>(); + for (int index = startDataPrefix; index < startDataPrefix + count; index++) { + result.add(memberWithMemberId(index)); + } + + return result; + } } \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java index 6ec83101e..0237e4212 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java @@ -16,7 +16,7 @@ public class MemberGroupFixture { * @param group 대상 그룹 * @return 그룹 멤버 테스트 픽스처 */ - public static MemberGroup memberGroup(Member member, Group group) { + public static MemberGroup groupOwner(Member member, Group group) { return MemberGroup.createGroupOwner(member, group); } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java index e3b158f37..ea715b5dd 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -11,6 +12,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -18,9 +20,14 @@ import site.timecapsulearchive.core.common.dependency.TestTransactionTemplate; import site.timecapsulearchive.core.common.fixture.domain.GroupFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; +import site.timecapsulearchive.core.common.fixture.domain.MemberGroupFixture; import site.timecapsulearchive.core.common.fixture.dto.GroupDtoFixture; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.group.entity.Group; +import site.timecapsulearchive.core.domain.group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; import site.timecapsulearchive.core.domain.group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupOwnerAuthenticateException; @@ -34,6 +41,7 @@ import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; +import site.timecapsulearchive.core.infra.s3.manager.S3ObjectManager; class GroupWriteServiceTest { @@ -44,6 +52,9 @@ class GroupWriteServiceTest { private final SocialNotificationManager socialNotificationManager = mock( SocialNotificationManager.class); private final TransactionTemplate transactionTemplate = TestTransactionTemplate.spied(); + private final GroupCapsuleQueryRepository groupCapsuleQueryRepository = mock( + GroupCapsuleQueryRepository.class); + private final S3ObjectManager s3ObjectManager = mock(S3ObjectManager.class); private final GroupWriteService groupWriteService = new GroupWriteServiceImpl( memberRepository, @@ -51,7 +62,9 @@ class GroupWriteServiceTest { memberGroupRepository, groupInviteRepository, transactionTemplate, - socialNotificationManager + socialNotificationManager, + groupCapsuleQueryRepository, + s3ObjectManager ); @Test @@ -99,6 +112,7 @@ class GroupWriteServiceTest { given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); given(memberRepository.findMemberById(targetId)).willReturn( Optional.of(MemberFixture.member(2))); + given(groupRepository.findGroupById(groupId)).willReturn(Optional.of(GroupFixture.group())); given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( Optional.of(groupOwnerSummaryDto)); @@ -143,6 +157,7 @@ class GroupWriteServiceTest { given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); given(memberRepository.findMemberById(targetId)).willReturn( Optional.of(MemberFixture.member(2))); + given(groupRepository.findGroupById(groupId)).willReturn(Optional.of(GroupFixture.group())); given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( Optional.of(groupOwnerSummaryDto)); @@ -228,6 +243,133 @@ class GroupWriteServiceTest { .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); } + @Test + void 존재하지_않는_그룹_아이디로_삭제를_시도하면_예외가_발생한다() { + //given + Long memberId = 1L; + Long notExistGroupId = 1L; + + given(groupRepository.findGroupById(anyLong())).willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> groupWriteService.deleteGroup(memberId, notExistGroupId)) + .isExactlyInstanceOf(GroupNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 그룹장이_아닌_사용자가_그룹_아이디로_삭제를_시도하면_예외가_발생한다() { + //given + Long groupMemberId = 1L; + Long groupId = 1L; + + given(groupRepository.findGroupById(anyLong())).willReturn(group()); + given(memberGroupRepository.findMemberGroupsByGroupId(groupId)).willReturn( + notOwnerGroupMember()); + + //when + //then + assertThatThrownBy(() -> groupWriteService.deleteGroup(groupMemberId, groupId)) + .isExactlyInstanceOf(GroupDeleteFailException.class) + .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); + } + + private List notOwnerGroupMember() { + return List.of( + MemberGroupFixture.memberGroup( + MemberFixture.memberWithMemberId(1), + GroupFixture.group(), + false + ) + ); + } + + @Test + void 그룹원이_존재하는_그룹_아이디로_삭제를_시도하면_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupMemberExistGroupId = 1L; + + given(groupRepository.findGroupById(anyLong())).willReturn(group()); + given(memberGroupRepository.findMemberGroupsByGroupId(groupMemberExistGroupId)).willReturn( + groupMembers()); + + //when + //then + assertThatThrownBy( + () -> groupWriteService.deleteGroup(groupOwnerId, groupMemberExistGroupId)) + .isExactlyInstanceOf(GroupDeleteFailException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_EXIST_ERROR.getMessage()); + } + + private Optional group() { + return Optional.of( + GroupFixture.group() + ); + } + + private List groupMembers() { + Group group = GroupFixture.group(); + MemberGroup groupOwner = MemberGroupFixture.groupOwner(MemberFixture.memberWithMemberId(1L), + group); + + List memberGroups = MemberGroupFixture.memberGroups( + MemberFixture.membersWithMemberId(2, 2), + group + ); + + List result = new ArrayList<>(memberGroups); + result.add(groupOwner); + return result; + } + + @Test + void 그룹_캡슐이_존재하는_그룹_아이디로_삭제를_시도하면_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupCapsuleExistGroupId = 1L; + + given(groupRepository.findGroupById(anyLong())).willReturn(group()); + given(memberGroupRepository.findMemberGroupsByGroupId(groupCapsuleExistGroupId)).willReturn( + ownerGroupMember()); + given(groupCapsuleQueryRepository.findGroupCapsuleExistByGroupId(anyLong())).willReturn( + true); + + //when + //then + assertThatThrownBy( + () -> groupWriteService.deleteGroup(groupOwnerId, groupCapsuleExistGroupId)) + .isExactlyInstanceOf(GroupDeleteFailException.class) + .hasMessageContaining(ErrorCode.GROUP_CAPSULE_EXIST_ERROR.getMessage()); + } + private List ownerGroupMember() { + return List.of( + MemberGroupFixture.groupOwner( + MemberFixture.memberWithMemberId(1), + GroupFixture.group() + ) + ); + } + + @Test + void 그룹을_삭제할_조건을_만족한_그룹_아이디로_삭제를_시도하면_그룹이_삭제된다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + + given(groupRepository.findGroupById(anyLong())).willReturn(group()); + given(memberGroupRepository.findMemberGroupsByGroupId(groupId)).willReturn( + ownerGroupMember()); + given(groupCapsuleQueryRepository.findGroupCapsuleExistByGroupId(anyLong())).willReturn( + false); + + //when + //then + assertThatCode( + () -> groupWriteService.deleteGroup(groupOwnerId, groupId)).doesNotThrowAnyException(); + verify(groupRepository, times(1)).delete(any(Group.class)); + } } From 46613bc01252acc7218035bc343b4c0f148d5170 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 00:08:49 +0900 Subject: [PATCH 3/8] =?UTF-8?q?docs=20:=20=EB=AC=B8=EC=84=9C=ED=99=94=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/api/GroupApi.java | 88 +++++++++---------- .../service/write/GroupWriteServiceImpl.java | 4 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java index ad2866260..0fd57eb75 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java @@ -23,6 +23,50 @@ public interface GroupApi { + @Operation( + summary = "그룹 삭제", + description = """ + 그룹 삭제를 요청한 사용자가 해당 그룹의 그룹장인 경우 그룹을 삭제한다.
+ 주의 +
    +
  • 그룹장이 아닌 경우 그룹을 삭제할 수 없다.
  • +
  • 그룹에 포함된 멤버가 그룹장을 제외하고 존재하면 그룹을 삭제할 수 없다.
  • +
  • 그룹에 그룹 캡슐이 남아있는 경우 삭제할 수 없다.
  • +
+ """, + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "처리 시작" + ), + @ApiResponse( + responseCode = "400", + description = """ + 다음의 경우 예외가 발생한다. +
    +
  • 삭제를 요청한 그룹에서 요청한 사용자가 그룹장이 아닌 경우
  • +
  • 삭제를 요청한 그룹에 그룹장을 제외한 그룹원이 존재하는 경우
  • +
  • 삭제를 요청한 그룹에 그룹 캡슐이 존재하는 경우
  • +
+ """, + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "그룹이 존재하지 않으면 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + }) + ResponseEntity> deleteGroupById( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "삭제할 그룹 아이디", required = true) + Long groupId + ); + @Operation( summary = "그룹 요청 수락", description = "특정 그룹으로부터 그룹 요청을 수락한다.", @@ -74,50 +118,6 @@ ResponseEntity> createGroup( GroupCreateRequest request ); - @Operation( - summary = "그룹 삭제", - description = """ - 그룹 삭제를 요청한 사용자가 해당 그룹의 그룹장인 경우 그룹을 삭제한다.
- 주의 -
    -
  • 그룹에 포함된 멤버가 그룹장을 제외하고 존재하면 그룹을 삭제할 수 없다.
  • -
  • 그룹장이 아닌 경우 그룹을 삭제할 수 없다.
  • -
  • 그룹에 그룹 캡슐이 남아있는 경우 삭제할 수 없다.
  • -
- """, - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "202", - description = "처리 시작" - ), - @ApiResponse( - responseCode = "400", - description = """ - 다음의 경우 예외가 발생한다. -
    -
  • 삭제를 요청한 그룹에 그룹장을 제외한 그룹원이 존재하는 경우
  • -
  • 삭제를 요청한 그룹에서 요청한 사용자가 그룹장이 아닌 경우
  • -
  • 삭제를 요청한 그룹에 그룹 캡슐이 존재하는 경우
  • -
- """, - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - @ApiResponse( - responseCode = "404", - description = "그룹이 존재하지 않으면 발생한다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - }) - ResponseEntity> deleteGroupById( - Long memberId, - - @Parameter(in = ParameterIn.PATH, description = "삭제할 그룹 아이디", required = true) - Long groupId - ); - @Operation( summary = "그룹원 삭제", description = "그룹장인 경우 특정 그룹원을 삭제한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java index 0c5e0a92b..768fc3f2a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java @@ -130,8 +130,8 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { * 사용자가 그룹장인 그룹을 삭제한다. *
주의 - 그룹 삭제 시 아래 조건에 해당하면 예외가 발생한다. *
1. 그룹이 존재하지 않는 경우 - *
2. 그룹에 멤버가 존재하는 경우 - *
3. 그룹 삭제를 요청한 사용자가 그룹장이 아닌 경우 + *
2. 그룹 삭제를 요청한 사용자가 그룹장이 아닌 경우 + *
3. 그룹에 멤버가 존재하는 경우 *
4. 그룹에 그룹 캡슐이 남아있는 경우 * * @param memberId 그룹에 속한 그룹장 아이디 From 1837e7a22743de9b64d652537eb470c70beefb1e Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 00:11:19 +0900 Subject: [PATCH 4/8] =?UTF-8?q?fix=20:=20cascade=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timecapsulearchive/core/domain/group/entity/Group.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/Group.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/Group.java index 08f7765bf..9f8e0eacd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/Group.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/Group.java @@ -38,12 +38,6 @@ public class Group extends BaseEntity { @Column(name = "group_profile_url", nullable = false) private String groupProfileUrl; - @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, orphanRemoval = true) - private List capsules; - - @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, orphanRemoval = true) - private List members; - @Builder private Group(String groupName, String groupDescription, String groupProfileUrl) { if (Objects.isNull(groupName) From 81823ca161a446ba7a109cf2471b5a8b359315fe Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 00:38:14 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix=20:=20lazy=20=EC=B6=94=EA=B0=80,=20@Mod?= =?UTF-8?q?ifying=20=EC=98=B5=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/entity/GroupInvite.java | 2 +- .../repository/groupInviteRepository/GroupInviteRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java index 7ff563148..140c53763 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java @@ -26,7 +26,7 @@ public class GroupInvite extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "group_id", nullable = false) private Group group; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java index 8b876cb9a..258563ef1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java @@ -16,7 +16,7 @@ int deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId(Long groupId, Long Long groupMemberId); @Query("delete from GroupInvite gi where gi.id in :groupInviteIds") - @Modifying(clearAutomatically = true) + @Modifying void bulkDelete(@Param("groupInviteIds") List groupInviteIds); } From f403e64bad05d6c13715d20e4939caa302db5b57 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 09:27:49 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix=20:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=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 --- .../domain/group/service/write/GroupWriteServiceImpl.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java index 768fc3f2a..88fa65842 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java @@ -138,9 +138,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { * @param groupId 그룹 아이디 */ public void deleteGroup(final Long memberId, final Long groupId) { - final String[] groupProfilePath = new String[1]; - - transactionTemplate.executeWithoutResult(transactionStatus -> { + final String groupProfilePath = transactionTemplate.execute(transactionStatus -> { final Group group = groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); @@ -156,10 +154,10 @@ public void deleteGroup(final Long memberId, final Long groupId) { groupInviteRepository.bulkDelete(groupInviteIds); groupRepository.delete(group); - groupProfilePath[0] = group.getGroupProfileUrl(); + return group.getGroupProfileUrl(); }); - s3ObjectManager.deleteObject(groupProfilePath[0]); + s3ObjectManager.deleteObject(groupProfilePath); } private void checkGroupOwnership(Long memberId, List groupMembers) { From 25d09ce593e650586056aff92cf79b58de0bca90 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:13:27 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix=20:=20read/write,=20group/group=5Fmembe?= =?UTF-8?q?r=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../facade/GroupCapsuleFacade.java | 6 +- .../core/domain/group/api/GroupApi.java | 274 ------------------ .../domain/group/api/GroupApiController.java | 175 ----------- .../group/api/command/GroupCommandApi.java | 109 +++++++ .../command/GroupCommandApiController.java | 63 ++++ .../domain/group/api/query/GroupQueryApi.java | 66 +++++ .../api/query/GroupQueryApiController.java | 73 +++++ .../GroupQueryRepository.java | 2 +- .../GroupQueryRepositoryImpl.java | 4 +- .../GroupRepository.java | 2 +- .../MemberGroupQueryRepository.java | 10 - .../domain/group/service/GroupService.java | 8 - .../group/service/GroupServiceImpl.java | 65 ----- .../GroupCommandService.java} | 82 +----- .../GroupQueryService.java} | 6 +- .../group/service/read/GroupReadService.java | 20 -- .../service/write/GroupWriteService.java | 16 - .../api/command/GroupMemberCommandApi.java | 128 ++++++++ .../GroupMemberCommandApiController.java | 78 +++++ .../data}/GroupOwnerSummaryDto.java | 2 +- .../entity/GroupInvite.java | 3 +- .../entity/MemberGroup.java | 3 +- .../GroupInviteNotFoundException.java | 2 +- .../GroupOwnerAuthenticateException.java | 2 +- .../GroupInviteQueryRepository.java | 2 +- .../GroupInviteQueryRepositoryImpl.java | 5 +- .../GroupInviteRepository.java | 4 +- .../MemberGroupQueryRepository.java | 10 + .../MemberGroupQueryRepositoryImpl.java | 6 +- .../MemberGroupRepository.java | 4 +- .../service/GroupMemberCommandService.java | 101 +++++++ .../core/domain/member/entity/Member.java | 2 +- .../repository/MemberQueryRepository.java | 2 +- 33 files changed, 664 insertions(+), 671 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApi.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApiController.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApi.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApiController.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{groupRepository => }/GroupQueryRepository.java (86%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{groupRepository => }/GroupQueryRepositoryImpl.java (95%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{groupRepository => }/GroupRepository.java (81%) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepository.java delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupService.java delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/{write/GroupWriteServiceImpl.java => command/GroupCommandService.java} (57%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/{read/GroupReadServiceImpl.java => query/GroupQueryService.java} (87%) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadService.java delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApi.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApiController.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group/data/dto => group_member/data}/GroupOwnerSummaryDto.java (64%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/entity/GroupInvite.java (92%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/entity/MemberGroup.java (92%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/exception/GroupInviteNotFoundException.java (82%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/exception/GroupOwnerAuthenticateException.java (82%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/repository/groupInviteRepository/GroupInviteQueryRepository.java (73%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java (91%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/repository/groupInviteRepository/GroupInviteRepository.java (80%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepository.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java (82%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group => group_member}/repository/memberGroupRepository/MemberGroupRepository.java (67%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandService.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java index ba2164109..de10d78e8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java @@ -13,7 +13,7 @@ import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.capsuleskin.service.CapsuleSkinService; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group.service.GroupService; +import site.timecapsulearchive.core.domain.group.service.query.GroupQueryService; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.service.MemberService; import site.timecapsulearchive.core.global.geography.GeoTransformManager; @@ -24,7 +24,7 @@ public class GroupCapsuleFacade { private final MemberService memberService; private final GroupCapsuleService groupCapsuleService; - private final GroupService groupService; + private final GroupQueryService groupQueryService; private final ImageService imageService; private final VideoService videoService; private final GeoTransformManager geoTransformManager; @@ -38,7 +38,7 @@ public void saveGroupCapsule( final Long groupId ) { final Member member = memberService.findMemberById(memberId); - final Group group = groupService.findGroupById(groupId); + final Group group = groupQueryService.findGroupById(groupId); final CapsuleSkin capsuleSkin = capsuleSkinService.findCapsuleSkinById(dto.capsuleSkinId()); final Point point = geoTransformManager.changePoint4326To3857(dto.latitude(), diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java deleted file mode 100644 index 0fd57eb75..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApi.java +++ /dev/null @@ -1,274 +0,0 @@ -package site.timecapsulearchive.core.domain.group.api; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import java.time.ZonedDateTime; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import site.timecapsulearchive.core.domain.group.data.reqeust.GroupCreateRequest; -import site.timecapsulearchive.core.domain.group.data.reqeust.GroupUpdateRequest; -import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; -import site.timecapsulearchive.core.domain.group.data.response.GroupsSliceResponse; -import site.timecapsulearchive.core.global.common.response.ApiSpec; -import site.timecapsulearchive.core.global.error.ErrorResponse; - -public interface GroupApi { - - @Operation( - summary = "그룹 삭제", - description = """ - 그룹 삭제를 요청한 사용자가 해당 그룹의 그룹장인 경우 그룹을 삭제한다.
- 주의 -
    -
  • 그룹장이 아닌 경우 그룹을 삭제할 수 없다.
  • -
  • 그룹에 포함된 멤버가 그룹장을 제외하고 존재하면 그룹을 삭제할 수 없다.
  • -
  • 그룹에 그룹 캡슐이 남아있는 경우 삭제할 수 없다.
  • -
- """, - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "202", - description = "처리 시작" - ), - @ApiResponse( - responseCode = "400", - description = """ - 다음의 경우 예외가 발생한다. -
    -
  • 삭제를 요청한 그룹에서 요청한 사용자가 그룹장이 아닌 경우
  • -
  • 삭제를 요청한 그룹에 그룹장을 제외한 그룹원이 존재하는 경우
  • -
  • 삭제를 요청한 그룹에 그룹 캡슐이 존재하는 경우
  • -
- """, - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - @ApiResponse( - responseCode = "404", - description = "그룹이 존재하지 않으면 발생한다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - }) - ResponseEntity> deleteGroupById( - Long memberId, - - @Parameter(in = ParameterIn.PATH, description = "삭제할 그룹 아이디", required = true) - Long groupId - ); - - @Operation( - summary = "그룹 요청 수락", - description = "특정 그룹으로부터 그룹 요청을 수락한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "처리 완료" - ), - @ApiResponse( - responseCode = "404", - description = "그룹 초대 찾기 실패" - ), - @ApiResponse( - responseCode = "500", - description = "외부 API 요청 실패" - ) - }) - ResponseEntity> acceptGroupInvitation( - Long memberId, - - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) - Long groupId, - - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) - Long targetId - ); - - @Operation( - summary = "그룹 생성", - description = "친구로부터 그룹을 생성한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "처리완료" - ), - @ApiResponse( - responseCode = "500", - description = "외부 API 요청 실패" - ) - }) - ResponseEntity> createGroup( - Long memberId, - GroupCreateRequest request - ); - - @Operation( - summary = "그룹원 삭제", - description = "그룹장인 경우 특정 그룹원을 삭제한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "204", - description = "처리 완료" - ) - }) - @DeleteMapping(value = "/groups/{group_id}/members/{member_id}") - ResponseEntity deleteGroupMember( - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, - - @Parameter(in = ParameterIn.PATH, description = "삭제할 멤버 아이디", required = true, schema = @Schema()) - @PathVariable("member_id") Long memberId - ); - - @Operation( - summary = "그룹 요청 거부", - description = "특정 그룹으로부터 초대 요청을 거부한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "처리 완료" - ) - }) - ResponseEntity> rejectGroupInvitation( - Long memberId, - - Long groupId, - - @Parameter(in = ParameterIn.PATH, description = "그룹 초대 대상 아이디", required = true) - Long groupOwnerId - ); - - @Operation( - summary = "그룹 상세 조회", - description = "그룹의 상세 정보를 보여준다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ), - @ApiResponse( - responseCode = "400", - description = "잘못된 파라미터를 받았을 때 발생하는 오류" - ), - @ApiResponse( - responseCode = "403", - description = "그룹에 포함된 사용자가 아닌 경우 발생하는 오류" - ) - }) - ResponseEntity> findGroupDetailById( - Long memberId, - - @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true, schema = @Schema()) - Long groupId - ); - - @Operation( - summary = "그룹 목록 조회", - description = "사용자가 소속된 그룹의 목록을 조회한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity> findGroups( - Long memberId, - - @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) - int size, - - @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) - ZonedDateTime createdAt - ); - - @Operation( - summary = "그룹 요청", - description = "그룹장인 경우 친구에게 그룹 가입 요청을 할 수 있다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "202", - description = "처리 시작" - ) - }) - ResponseEntity> inviteGroup( - Long memberId, - - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) - Long groupId, - - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) - Long targetId - ); - - @Operation( - summary = "그룹 탈퇴", - description = "사용자가 속한 그룹을 탈퇴한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "204", - description = "처리 완료" - ) - }) - @DeleteMapping(value = "/groups/{group_id}/members/quit") - ResponseEntity quitGroup( - @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId - ); - - @Operation( - summary = "그룹 수정", - description = "그룹장인 경우에 그룹의 기본 정보들을 수정한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "202", - description = "처리 시작" - ) - }) - @PatchMapping( - value = "/groups/{group_id}", - consumes = {"multipart/form-data"} - ) - ResponseEntity updateGroupById( - @Parameter(in = ParameterIn.PATH, description = "수정할 그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, - - @ModelAttribute GroupUpdateRequest request - ); -} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java deleted file mode 100644 index bf7e50c5d..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java +++ /dev/null @@ -1,175 +0,0 @@ -package site.timecapsulearchive.core.domain.group.api; - -import jakarta.validation.Valid; -import java.time.ZonedDateTime; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; -import site.timecapsulearchive.core.domain.group.data.reqeust.GroupCreateRequest; -import site.timecapsulearchive.core.domain.group.data.reqeust.GroupUpdateRequest; -import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; -import site.timecapsulearchive.core.domain.group.data.response.GroupsSliceResponse; -import site.timecapsulearchive.core.domain.group.service.GroupService; -import site.timecapsulearchive.core.global.common.response.ApiSpec; -import site.timecapsulearchive.core.global.common.response.SuccessCode; -import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; -import site.timecapsulearchive.core.infra.s3.manager.S3UrlGenerator; - - -@RestController -@RequestMapping("/groups") -@RequiredArgsConstructor -public class GroupApiController implements GroupApi { - - private final GroupService groupService; - private final S3UrlGenerator s3UrlGenerator; - private final S3PreSignedUrlManager s3PreSignedUrlManager; - - @PostMapping(value = "/accept/{group_id}/member/{target_id}") - @Override - public ResponseEntity> acceptGroupInvitation( - @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId, - @PathVariable("target_id") final Long targetId - ) { - groupService.acceptGroupInvite(memberId, groupId, targetId); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.ACCEPTED - ) - ); - } - - @Override - @PostMapping - public ResponseEntity> createGroup( - @AuthenticationPrincipal final Long memberId, - @Valid @RequestBody GroupCreateRequest request - ) { - final String groupProfileUrl = s3UrlGenerator.generateFileName(memberId, - request.groupDirectory(), request.groupImage()); - final GroupCreateDto dto = request.toDto(groupProfileUrl); - - groupService.createGroup(memberId, dto); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.ACCEPTED - ) - ); - } - - @DeleteMapping(value = "/{group_id}") - @Override - public ResponseEntity> deleteGroupById( - @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId - ) { - groupService.deleteGroup(memberId, groupId); - - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); - } - - @Override - public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { - return null; - } - - @DeleteMapping(value = "/reject/{group_id}/member/{target_id}") - public ResponseEntity> rejectGroupInvitation( - @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId, - @PathVariable("target_id") final Long targetId) { - - groupService.rejectRequestGroup(memberId, groupId, targetId); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.SUCCESS - ) - ); - } - - @GetMapping( - value = "/{group_id}", - produces = {"application/json"} - ) - @Override - public ResponseEntity> findGroupDetailById( - @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId - ) { - final GroupDetailDto groupDetailDto = groupService.findGroupDetailByGroupId(memberId, - groupId); - - return ResponseEntity.ok( - ApiSpec.success( - SuccessCode.SUCCESS, - groupDetailDto.toResponse(s3PreSignedUrlManager::getS3PreSignedUrlForGet) - ) - ); - } - - @GetMapping( - produces = {"application/json"} - ) - @Override - public ResponseEntity> findGroups( - @AuthenticationPrincipal final Long memberId, - @RequestParam(defaultValue = "20", value = "size") final int size, - @RequestParam(value = "created_at") final ZonedDateTime createdAt - ) { - final Slice groupsSlice = groupService.findGroupsSlice(memberId, size, - createdAt); - - return ResponseEntity.ok( - ApiSpec.success( - SuccessCode.SUCCESS, - GroupsSliceResponse.createOf( - groupsSlice.getContent(), - groupsSlice.hasNext(), - s3PreSignedUrlManager::getS3PreSignedUrlForGet - ) - ) - ); - } - - @PostMapping(value = "/invite/{group_id}/member/{target_id}") - @Override - public ResponseEntity> inviteGroup( - @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId, - @PathVariable("target_id") final Long targetId - ) { - groupService.inviteGroup(memberId, groupId, targetId); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.ACCEPTED - ) - ); - } - - @Override - public ResponseEntity quitGroup(Long groupId) { - return null; - } - - @Override - public ResponseEntity updateGroupById(Long groupId, GroupUpdateRequest request) { - return null; - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApi.java new file mode 100644 index 000000000..bee129442 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApi.java @@ -0,0 +1,109 @@ +package site.timecapsulearchive.core.domain.group.api.command; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import site.timecapsulearchive.core.domain.group.data.reqeust.GroupCreateRequest; +import site.timecapsulearchive.core.domain.group.data.reqeust.GroupUpdateRequest; +import site.timecapsulearchive.core.global.common.response.ApiSpec; +import site.timecapsulearchive.core.global.error.ErrorResponse; + +public interface GroupCommandApi { + + @Operation( + summary = "그룹 삭제", + description = """ + 그룹 삭제를 요청한 사용자가 해당 그룹의 그룹장인 경우 그룹을 삭제한다.
+ 주의 +
    +
  • 그룹장이 아닌 경우 그룹을 삭제할 수 없다.
  • +
  • 그룹에 포함된 멤버가 그룹장을 제외하고 존재하면 그룹을 삭제할 수 없다.
  • +
  • 그룹에 그룹 캡슐이 남아있는 경우 삭제할 수 없다.
  • +
+ """, + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "처리 시작" + ), + @ApiResponse( + responseCode = "400", + description = """ + 다음의 경우 예외가 발생한다. +
    +
  • 삭제를 요청한 그룹에서 요청한 사용자가 그룹장이 아닌 경우
  • +
  • 삭제를 요청한 그룹에 그룹장을 제외한 그룹원이 존재하는 경우
  • +
  • 삭제를 요청한 그룹에 그룹 캡슐이 존재하는 경우
  • +
+ """, + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "그룹이 존재하지 않으면 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + }) + ResponseEntity> deleteGroupById( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "삭제할 그룹 아이디", required = true) + Long groupId + ); + + @Operation( + summary = "그룹 생성", + description = "친구로부터 그룹을 생성한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리완료" + ), + @ApiResponse( + responseCode = "500", + description = "외부 API 요청 실패" + ) + }) + ResponseEntity> createGroup( + Long memberId, + GroupCreateRequest request + ); + + @Operation( + summary = "그룹 수정", + description = "그룹장인 경우에 그룹의 기본 정보들을 수정한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "처리 시작" + ) + }) + @PatchMapping( + value = "/groups/{group_id}", + consumes = {"multipart/form-data"} + ) + ResponseEntity updateGroupById( + @Parameter(in = ParameterIn.PATH, description = "수정할 그룹 아이디", required = true, schema = @Schema()) + @PathVariable("group_id") Long groupId, + + @ModelAttribute GroupUpdateRequest request + ); +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApiController.java new file mode 100644 index 000000000..3dc4ade88 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/command/GroupCommandApiController.java @@ -0,0 +1,63 @@ +package site.timecapsulearchive.core.domain.group.api.command; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; +import site.timecapsulearchive.core.domain.group.data.reqeust.GroupCreateRequest; +import site.timecapsulearchive.core.domain.group.data.reqeust.GroupUpdateRequest; +import site.timecapsulearchive.core.domain.group.service.command.GroupCommandService; +import site.timecapsulearchive.core.global.common.response.ApiSpec; +import site.timecapsulearchive.core.global.common.response.SuccessCode; +import site.timecapsulearchive.core.infra.s3.manager.S3UrlGenerator; + +@RestController +@RequestMapping("/groups") +@RequiredArgsConstructor +public class GroupCommandApiController implements GroupCommandApi { + + private final GroupCommandService groupCommandService; + private final S3UrlGenerator s3UrlGenerator; + + @Override + @PostMapping + public ResponseEntity> createGroup( + @AuthenticationPrincipal final Long memberId, + @Valid @RequestBody GroupCreateRequest request + ) { + final String groupProfileUrl = s3UrlGenerator.generateFileName(memberId, + request.groupDirectory(), request.groupImage()); + final GroupCreateDto dto = request.toDto(groupProfileUrl); + + groupCommandService.createGroup(memberId, dto); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); + } + + @DeleteMapping(value = "/{group_id}") + @Override + public ResponseEntity> deleteGroupById( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId + ) { + groupCommandService.deleteGroup(memberId, groupId); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + } + + @Override + public ResponseEntity updateGroupById(Long groupId, GroupUpdateRequest request) { + return null; + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApi.java new file mode 100644 index 000000000..6c67826dc --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApi.java @@ -0,0 +1,66 @@ +package site.timecapsulearchive.core.domain.group.api.query; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import java.time.ZonedDateTime; +import org.springframework.http.ResponseEntity; +import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; +import site.timecapsulearchive.core.domain.group.data.response.GroupsSliceResponse; +import site.timecapsulearchive.core.global.common.response.ApiSpec; + +public interface GroupQueryApi { + + @Operation( + summary = "그룹 상세 조회", + description = "그룹의 상세 정보를 보여준다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 파라미터를 받았을 때 발생하는 오류" + ), + @ApiResponse( + responseCode = "403", + description = "그룹에 포함된 사용자가 아닌 경우 발생하는 오류" + ) + }) + ResponseEntity> findGroupDetailById( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true, schema = @Schema()) + Long groupId + ); + + @Operation( + summary = "그룹 목록 조회", + description = "사용자가 소속된 그룹의 목록을 조회한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findGroups( + Long memberId, + + @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) + int size, + + @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) + ZonedDateTime createdAt + ); +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApiController.java new file mode 100644 index 000000000..befb57525 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/query/GroupQueryApiController.java @@ -0,0 +1,73 @@ +package site.timecapsulearchive.core.domain.group.api.query; + +import java.time.ZonedDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; +import site.timecapsulearchive.core.domain.group.data.response.GroupsSliceResponse; +import site.timecapsulearchive.core.domain.group.service.query.GroupQueryService; +import site.timecapsulearchive.core.global.common.response.ApiSpec; +import site.timecapsulearchive.core.global.common.response.SuccessCode; +import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; + +@RestController +@RequestMapping("/groups") +@RequiredArgsConstructor +public class GroupQueryApiController implements GroupQueryApi { + + private final GroupQueryService groupQueryService; + private final S3PreSignedUrlManager s3PreSignedUrlManager; + + @GetMapping( + value = "/{group_id}", + produces = {"application/json"} + ) + @Override + public ResponseEntity> findGroupDetailById( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId + ) { + final GroupDetailDto groupDetailDto = groupQueryService.findGroupDetailByGroupId(memberId, + groupId); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + groupDetailDto.toResponse(s3PreSignedUrlManager::getS3PreSignedUrlForGet) + ) + ); + } + + @GetMapping( + produces = {"application/json"} + ) + @Override + public ResponseEntity> findGroups( + @AuthenticationPrincipal final Long memberId, + @RequestParam(defaultValue = "20", value = "size") final int size, + @RequestParam(value = "created_at") final ZonedDateTime createdAt + ) { + final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, size, + createdAt); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + GroupsSliceResponse.createOf( + groupsSlice.getContent(), + groupsSlice.hasNext(), + s3PreSignedUrlManager::getS3PreSignedUrlForGet + ) + ) + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java similarity index 86% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index 0ce6327a9..a19bcb478 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository.groupRepository; +package site.timecapsulearchive.core.domain.group.repository; import java.time.ZonedDateTime; import java.util.Optional; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java similarity index 95% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index 21ff3e10b..f24ce647b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -1,9 +1,9 @@ -package site.timecapsulearchive.core.domain.group.repository.groupRepository; +package site.timecapsulearchive.core.domain.group.repository; import static com.querydsl.core.group.GroupBy.groupBy; import static com.querydsl.core.group.GroupBy.list; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; -import static site.timecapsulearchive.core.domain.group.entity.QMemberGroup.memberGroup; +import static site.timecapsulearchive.core.domain.group_member.entity.QMemberGroup.memberGroup; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import com.querydsl.core.types.Projections; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java similarity index 81% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java index ac60cac3c..d8ddde133 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository.groupRepository; +package site.timecapsulearchive.core.domain.group.repository; import java.util.Optional; import org.springframework.data.repository.Repository; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepository.java deleted file mode 100644 index c39ef58b2..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package site.timecapsulearchive.core.domain.group.repository.memberGroupRepository; - -import java.util.Optional; -import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; - -public interface MemberGroupQueryRepository { - - Optional findOwnerInMemberGroup(Long groupId, Long memberId); - -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupService.java deleted file mode 100644 index 5fa63bedf..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupService.java +++ /dev/null @@ -1,8 +0,0 @@ -package site.timecapsulearchive.core.domain.group.service; - -import site.timecapsulearchive.core.domain.group.service.read.GroupReadService; -import site.timecapsulearchive.core.domain.group.service.write.GroupWriteService; - -public interface GroupService extends GroupReadService, GroupWriteService { - -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java deleted file mode 100644 index c4a66d617..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -package site.timecapsulearchive.core.domain.group.service; - -import java.time.ZonedDateTime; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; -import org.springframework.stereotype.Service; -import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; -import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group.service.read.GroupReadService; -import site.timecapsulearchive.core.domain.group.service.write.GroupWriteService; - -@Service -@RequiredArgsConstructor -public class GroupServiceImpl implements GroupService { - - private final GroupReadService groupReadService; - private final GroupWriteService groupWriteService; - - @Override - public Group findGroupById(final Long groupId) { - return groupReadService.findGroupById(groupId); - } - - @Override - public Slice findGroupsSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ) { - return groupReadService.findGroupsSlice(memberId, size, createdAt); - } - - @Override - public GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { - return groupReadService.findGroupDetailByGroupId(memberId, groupId); - } - - @Override - public void createGroup(final Long memberId, final GroupCreateDto dto) { - groupWriteService.createGroup(memberId, dto); - } - - @Override - public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { - groupWriteService.inviteGroup(memberId, groupId, targetId); - } - - @Override - public void rejectRequestGroup(final Long groupMemberId, final Long groupId, - final Long targetId) { - groupWriteService.rejectRequestGroup(groupMemberId, groupId, targetId); - } - - @Override - public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { - groupWriteService.acceptGroupInvite(memberId, groupId, targetId); - } - - @Override - public void deleteGroup(final Long memberId, final Long groupId) { - groupWriteService.deleteGroup(memberId, groupId); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java similarity index 57% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index 88fa65842..d1afbc627 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -1,25 +1,20 @@ -package site.timecapsulearchive.core.domain.group.service.write; +package site.timecapsulearchive.core.domain.group.service.command; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group.entity.GroupInvite; -import site.timecapsulearchive.core.domain.group.entity.MemberGroup; import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; -import site.timecapsulearchive.core.domain.group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; -import site.timecapsulearchive.core.domain.group.exception.GroupOwnerAuthenticateException; -import site.timecapsulearchive.core.domain.group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.group.repository.groupRepository.GroupRepository; -import site.timecapsulearchive.core.domain.group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.group.repository.GroupRepository; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; @@ -29,7 +24,7 @@ @Service @RequiredArgsConstructor -public class GroupWriteServiceImpl implements GroupWriteService { +public class GroupCommandService { private final MemberRepository memberRepository; private final GroupRepository groupRepository; @@ -61,71 +56,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { dto.groupProfileUrl(), dto.targetIds()); } - public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { - final Member groupOwner = memberRepository.findMemberById(memberId).orElseThrow( - MemberNotFoundException::new); - - final Member groupMember = memberRepository.findMemberById(targetId).orElseThrow( - MemberNotFoundException::new); - - final Group group = groupRepository.findGroupById(groupId) - .orElseThrow(GroupNotFoundException::new); - - final GroupInvite groupInvite = GroupInvite.createOf(group, groupOwner, groupMember); - - final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; - - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - summaryDto[0] = memberGroupRepository.findOwnerInMemberGroup( - groupId, memberId).orElseThrow(GroupNotFoundException::new); - - if (!summaryDto[0].isOwner()) { - throw new GroupOwnerAuthenticateException(); - } - - groupInviteRepository.save(groupInvite); - } - }); - - socialNotificationManager.sendGroupInviteMessage(summaryDto[0].nickname(), - summaryDto[0].groupProfileUrl(), List.of(targetId)); - } - - @Transactional - public void rejectRequestGroup(final Long memberId, final Long groupId, final Long targetId) { - final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId); - - if (isDenyRequest != 1) { - throw new GroupInviteNotFoundException(); - } - } - - public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { - final Member groupMember = memberRepository.findMemberById(memberId) - .orElseThrow(MemberNotFoundException::new); - final Group group = groupRepository.findGroupById(groupId) - .orElseThrow(GroupNotFoundException::new); - - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId); - - if (isDenyRequest != 1) { - throw new GroupInviteNotFoundException(); - } - - memberGroupRepository.save(MemberGroup.createGroupMember(groupMember, group)); - } - }); - - socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), targetId); - } - /** * 사용자가 그룹장인 그룹을 삭제한다. *
주의 - 그룹 삭제 시 아래 조건에 해당하면 예외가 발생한다. diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java similarity index 87% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadServiceImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index d3066d287..29049385f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.service.read; +package site.timecapsulearchive.core.domain.group.service.query; import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; @@ -9,12 +9,12 @@ import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; -import site.timecapsulearchive.core.domain.group.repository.groupRepository.GroupRepository; +import site.timecapsulearchive.core.domain.group.repository.GroupRepository; @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class GroupReadServiceImpl implements GroupReadService { +public class GroupQueryService { private final GroupRepository groupRepository; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadService.java deleted file mode 100644 index 635b1dbb1..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadService.java +++ /dev/null @@ -1,20 +0,0 @@ -package site.timecapsulearchive.core.domain.group.service.read; - -import java.time.ZonedDateTime; -import org.springframework.data.domain.Slice; -import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; -import site.timecapsulearchive.core.domain.group.entity.Group; - -public interface GroupReadService { - - Group findGroupById(final Long groupId); - - Slice findGroupsSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ); - - GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId); -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java deleted file mode 100644 index d7eb52fcb..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java +++ /dev/null @@ -1,16 +0,0 @@ -package site.timecapsulearchive.core.domain.group.service.write; - -import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; - -public interface GroupWriteService { - - void createGroup(final Long memberId, final GroupCreateDto dto); - - void inviteGroup(final Long memberId, final Long groupId, final Long targetId); - - void rejectRequestGroup(final Long memberId, final Long groupId, final Long targetId); - - void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId); - - void deleteGroup(final Long memberId, final Long groupId); -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApi.java new file mode 100644 index 000000000..e32783d4b --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApi.java @@ -0,0 +1,128 @@ +package site.timecapsulearchive.core.domain.group_member.api.command; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import site.timecapsulearchive.core.global.common.response.ApiSpec; + +public interface GroupMemberCommandApi { + + @Operation( + summary = "그룹원 삭제", + description = "그룹장인 경우 특정 그룹원을 삭제한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group member"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "처리 완료" + ) + }) + @DeleteMapping(value = "/groups/{group_id}/members/{member_id}") + ResponseEntity deleteGroupMember( + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true, schema = @Schema()) + @PathVariable("group_id") Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "삭제할 멤버 아이디", required = true, schema = @Schema()) + @PathVariable("member_id") Long memberId + ); + + @Operation( + summary = "그룹 탈퇴", + description = "사용자가 속한 그룹을 탈퇴한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group member"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "처리 완료" + ) + }) + @DeleteMapping(value = "/groups/{group_id}/members/quit") + ResponseEntity quitGroup( + @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true, schema = @Schema()) + @PathVariable("group_id") Long groupId + ); + + @Operation( + summary = "그룹 요청", + description = "그룹장인 경우 친구에게 그룹 가입 요청을 할 수 있다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group member"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "202", + description = "처리 시작" + ) + }) + ResponseEntity> inviteGroup( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) + Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) + Long targetId + ); + + @Operation( + summary = "그룹 요청 수락", + description = "특정 그룹으로부터 그룹 요청을 수락한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group member"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리 완료" + ), + @ApiResponse( + responseCode = "404", + description = "그룹 초대 찾기 실패" + ), + @ApiResponse( + responseCode = "500", + description = "외부 API 요청 실패" + ) + }) + ResponseEntity> acceptGroupInvitation( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) + Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) + Long targetId + ); + + @Operation( + summary = "그룹 요청 거부", + description = "특정 그룹으로부터 초대 요청을 거부한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group member"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리 완료" + ) + }) + ResponseEntity> rejectGroupInvitation( + Long memberId, + + Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "그룹 초대 대상 아이디", required = true) + Long groupOwnerId + ); +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApiController.java new file mode 100644 index 000000000..be6d3795b --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApiController.java @@ -0,0 +1,78 @@ +package site.timecapsulearchive.core.domain.group_member.api.command; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.domain.group_member.service.GroupMemberCommandService; +import site.timecapsulearchive.core.global.common.response.ApiSpec; +import site.timecapsulearchive.core.global.common.response.SuccessCode; + +@RestController +@RequestMapping("/group-members") +@RequiredArgsConstructor +public class GroupMemberCommandApiController implements GroupMemberCommandApi { + + private final GroupMemberCommandService groupMemberCommandService; + + @Override + public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { + return null; + } + + @Override + public ResponseEntity quitGroup(Long groupId) { + return null; + } + + @PostMapping(value = "/invite/{group_id}/member/{target_id}") + @Override + public ResponseEntity> inviteGroup( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId, + @PathVariable("target_id") final Long targetId + ) { + groupMemberCommandService.inviteGroup(memberId, groupId, targetId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); + } + + @PostMapping(value = "/accept/{group_id}/member/{target_id}") + @Override + public ResponseEntity> acceptGroupInvitation( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId, + @PathVariable("target_id") final Long targetId + ) { + groupMemberCommandService.acceptGroupInvite(memberId, groupId, targetId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); + } + + @DeleteMapping(value = "/reject/{group_id}/member/{target_id}") + public ResponseEntity> rejectGroupInvitation( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId, + @PathVariable("target_id") final Long targetId) { + + groupMemberCommandService.rejectRequestGroup(memberId, groupId, targetId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupOwnerSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/data/GroupOwnerSummaryDto.java similarity index 64% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupOwnerSummaryDto.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/data/GroupOwnerSummaryDto.java index 3496d3ec8..cbe65a301 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupOwnerSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/data/GroupOwnerSummaryDto.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.data.dto; +package site.timecapsulearchive.core.domain.group_member.data; public record GroupOwnerSummaryDto( String nickname, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/GroupInvite.java similarity index 92% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/GroupInvite.java index 140c53763..02a39f238 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/GroupInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/GroupInvite.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.entity; +package site.timecapsulearchive.core.domain.group_member.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -12,6 +12,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.entity.BaseEntity; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/MemberGroup.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/MemberGroup.java similarity index 92% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/MemberGroup.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/MemberGroup.java index 67c37404c..c1c8b3cec 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/entity/MemberGroup.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/MemberGroup.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.entity; +package site.timecapsulearchive.core.domain.group_member.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -12,6 +12,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.entity.BaseEntity; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupInviteNotFoundException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupInviteNotFoundException.java similarity index 82% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupInviteNotFoundException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupInviteNotFoundException.java index 39ad8bb61..41201b335 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupInviteNotFoundException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupInviteNotFoundException.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.exception; +package site.timecapsulearchive.core.domain.group_member.exception; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.BusinessException; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupOwnerAuthenticateException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupOwnerAuthenticateException.java similarity index 82% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupOwnerAuthenticateException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupOwnerAuthenticateException.java index 2c6382b85..ae2cc2b2f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupOwnerAuthenticateException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupOwnerAuthenticateException.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.exception; +package site.timecapsulearchive.core.domain.group_member.exception; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.BusinessException; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepository.java similarity index 73% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepository.java index 279dd9d5e..de9bbc52d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository; import java.util.List; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java similarity index 91% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 6f6c78ec2..6e1c973dd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -1,6 +1,7 @@ -package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository; -import static site.timecapsulearchive.core.domain.group.entity.QGroupInvite.groupInvite; + +import static site.timecapsulearchive.core.domain.group_member.entity.QGroupInvite.groupInvite; import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteRepository.java similarity index 80% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteRepository.java index 258563ef1..f411caa1a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteRepository.java @@ -1,11 +1,11 @@ -package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository; import java.util.List; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param; -import site.timecapsulearchive.core.domain.group.entity.GroupInvite; +import site.timecapsulearchive.core.domain.group_member.entity.GroupInvite; public interface GroupInviteRepository extends Repository, GroupInviteQueryRepository { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepository.java new file mode 100644 index 000000000..01e11dff6 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -0,0 +1,10 @@ +package site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository; + +import java.util.Optional; +import site.timecapsulearchive.core.domain.group_member.data.GroupOwnerSummaryDto; + +public interface MemberGroupQueryRepository { + + Optional findOwnerInMemberGroup(Long groupId, Long memberId); + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java similarity index 82% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 6e240515a..afdb563ff 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -1,7 +1,7 @@ -package site.timecapsulearchive.core.domain.group.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; -import static site.timecapsulearchive.core.domain.group.entity.QMemberGroup.memberGroup; +import static site.timecapsulearchive.core.domain.group_member.entity.QMemberGroup.memberGroup; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import com.querydsl.core.types.Projections; @@ -9,7 +9,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.group_member.data.GroupOwnerSummaryDto; @Repository @RequiredArgsConstructor diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java similarity index 67% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java index fdd73b90d..cfe9af47e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java @@ -1,8 +1,8 @@ -package site.timecapsulearchive.core.domain.group.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository; import java.util.List; import org.springframework.data.repository.Repository; -import site.timecapsulearchive.core.domain.group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; public interface MemberGroupRepository extends Repository, MemberGroupQueryRepository { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandService.java new file mode 100644 index 000000000..5e85ba734 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandService.java @@ -0,0 +1,101 @@ +package site.timecapsulearchive.core.domain.group_member.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; +import site.timecapsulearchive.core.domain.group_member.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.group.entity.Group; +import site.timecapsulearchive.core.domain.group_member.entity.GroupInvite; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; +import site.timecapsulearchive.core.domain.group_member.exception.GroupOwnerAuthenticateException; +import site.timecapsulearchive.core.domain.group.repository.GroupRepository; +import site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; + +@Service +@RequiredArgsConstructor +public class GroupMemberCommandService { + + private final MemberRepository memberRepository; + private final GroupRepository groupRepository; + private final MemberGroupRepository memberGroupRepository; + private final GroupInviteRepository groupInviteRepository; + private final TransactionTemplate transactionTemplate; + private final SocialNotificationManager socialNotificationManager; + + public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { + final Member groupOwner = memberRepository.findMemberById(memberId).orElseThrow( + MemberNotFoundException::new); + + final Member groupMember = memberRepository.findMemberById(targetId).orElseThrow( + MemberNotFoundException::new); + + final Group group = groupRepository.findGroupById(groupId) + .orElseThrow(GroupNotFoundException::new); + + final GroupInvite groupInvite = GroupInvite.createOf(group, groupOwner, groupMember); + + final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; + + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + summaryDto[0] = memberGroupRepository.findOwnerInMemberGroup( + groupId, memberId).orElseThrow(GroupNotFoundException::new); + + if (!summaryDto[0].isOwner()) { + throw new GroupOwnerAuthenticateException(); + } + + groupInviteRepository.save(groupInvite); + } + }); + + socialNotificationManager.sendGroupInviteMessage(summaryDto[0].nickname(), + summaryDto[0].groupProfileUrl(), List.of(targetId)); + } + + @Transactional + public void rejectRequestGroup(final Long memberId, final Long groupId, final Long targetId) { + final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId); + + if (isDenyRequest != 1) { + throw new GroupInviteNotFoundException(); + } + } + + public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { + final Member groupMember = memberRepository.findMemberById(memberId) + .orElseThrow(MemberNotFoundException::new); + final Group group = groupRepository.findGroupById(groupId) + .orElseThrow(GroupNotFoundException::new); + + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId); + + if (isDenyRequest != 1) { + throw new GroupInviteNotFoundException(); + } + + memberGroupRepository.save(MemberGroup.createGroupMember(groupMember, group)); + } + }); + + socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), targetId); + } + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java index a99bd8fff..432cb03d1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java @@ -19,7 +19,7 @@ import site.timecapsulearchive.core.domain.capsule.entity.Capsule; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; -import site.timecapsulearchive.core.domain.group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; import site.timecapsulearchive.core.domain.history.entity.History; import site.timecapsulearchive.core.global.entity.BaseEntity; import site.timecapsulearchive.core.global.util.NullCheck; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java index 56d405fa0..cb36a5ec6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java @@ -1,7 +1,7 @@ package site.timecapsulearchive.core.domain.member.repository; import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; -import static site.timecapsulearchive.core.domain.group.entity.QMemberGroup.memberGroup; +import static site.timecapsulearchive.core.domain.group_member.entity.QMemberGroup.memberGroup; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member.entity.QNotification.notification; import static site.timecapsulearchive.core.domain.member.entity.QNotificationCategory.notificationCategory; From 8e0155f5aaa57116a2525467d9309a711f0529c9 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:16:29 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/domain/MemberGroupFixture.java | 2 +- .../common/fixture/dto/GroupDtoFixture.java | 2 +- .../GroupCapsuleQueryRepositoryTest.java | 2 +- .../MemberGroupQueryRepositoryTest.java | 4 +- ...Test.java => GroupCommandServiceTest.java} | 178 ++-------------- .../GroupMemberCommandServiceTest.java | 196 ++++++++++++++++++ 6 files changed, 215 insertions(+), 169 deletions(-) rename backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/{GroupWriteServiceTest.java => GroupCommandServiceTest.java} (52%) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandServiceTest.java diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java index 0237e4212..68b03fbaf 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberGroupFixture.java @@ -4,7 +4,7 @@ import java.lang.reflect.Field; import java.util.List; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; import site.timecapsulearchive.core.domain.member.entity.Member; public class MemberGroupFixture { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupDtoFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupDtoFixture.java index ca4196785..ffe3c0252 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupDtoFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupDtoFixture.java @@ -2,7 +2,7 @@ import java.util.List; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.group_member.data.GroupOwnerSummaryDto; public class GroupDtoFixture { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java index 7965e63a6..0a9d98dfe 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java @@ -33,7 +33,7 @@ import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryTest.java index 08f0234d4..c09ecd314 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryTest.java @@ -25,9 +25,7 @@ import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group.entity.MemberGroup; -import site.timecapsulearchive.core.domain.group.repository.groupRepository.GroupQueryRepository; -import site.timecapsulearchive.core.domain.group.repository.groupRepository.GroupQueryRepositoryImpl; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java similarity index 52% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java index ea715b5dd..f8246fc22 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java @@ -24,26 +24,21 @@ import site.timecapsulearchive.core.common.fixture.dto.GroupDtoFixture; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group.entity.MemberGroup; import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; -import site.timecapsulearchive.core.domain.group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; -import site.timecapsulearchive.core.domain.group.exception.GroupOwnerAuthenticateException; -import site.timecapsulearchive.core.domain.group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.group.repository.groupRepository.GroupRepository; -import site.timecapsulearchive.core.domain.group.repository.memberGroupRepository.MemberGroupRepository; -import site.timecapsulearchive.core.domain.group.service.write.GroupWriteService; -import site.timecapsulearchive.core.domain.group.service.write.GroupWriteServiceImpl; -import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.group.repository.GroupRepository; +import site.timecapsulearchive.core.domain.group.service.command.GroupCommandService; +import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; import site.timecapsulearchive.core.infra.s3.manager.S3ObjectManager; -class GroupWriteServiceTest { +class GroupCommandServiceTest { private final MemberRepository memberRepository = mock(MemberRepository.class); private final GroupRepository groupRepository = mock(GroupRepository.class); @@ -56,7 +51,7 @@ class GroupWriteServiceTest { GroupCapsuleQueryRepository.class); private final S3ObjectManager s3ObjectManager = mock(S3ObjectManager.class); - private final GroupWriteService groupWriteService = new GroupWriteServiceImpl( + private final GroupCommandService groupCommandService = new GroupCommandService( memberRepository, groupRepository, memberGroupRepository, @@ -77,7 +72,7 @@ class GroupWriteServiceTest { Optional.of(MemberFixture.member(1))); //when - groupWriteService.createGroup(memberId, dto); + groupCommandService.createGroup(memberId, dto); //then verify(socialNotificationManager, times(1)).sendGroupInviteMessage( @@ -94,155 +89,11 @@ class GroupWriteServiceTest { //when //then - assertThatThrownBy(() -> groupWriteService.createGroup(memberId, dto)) + assertThatThrownBy(() -> groupCommandService.createGroup(memberId, dto)) .isInstanceOf(MemberNotFoundException.class) .hasMessageContaining(ErrorCode.MEMBER_NOT_FOUND_ERROR.getMessage()); } - - @Test - void 그룹장이_그룹원에게_그룹초대를_하면_그룹초대_알림이_요청된다() { - //given - Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; - Member groupOwner = MemberFixture.member(1); - GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(true); - - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); - given(memberRepository.findMemberById(targetId)).willReturn( - Optional.of(MemberFixture.member(2))); - given(groupRepository.findGroupById(groupId)).willReturn(Optional.of(GroupFixture.group())); - given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( - Optional.of(groupOwnerSummaryDto)); - - //when - groupWriteService.inviteGroup(memberId, groupId, targetId); - - //then - verify(socialNotificationManager, times(1)).sendGroupInviteMessage(anyString(), anyString(), - anyList()); - } - - @Test - void 그룹장이_그룹초대를_할_때_존재하지_않은_그룹_아이디_이면_예외가_발생한다() { - //given - Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; - Member groupOwner = MemberFixture.member(1); - - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); - given(memberRepository.findMemberById(targetId)).willReturn( - Optional.of(MemberFixture.member(2))); - given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( - Optional.empty()); - - //when - //then - assertThatThrownBy(() -> groupWriteService.inviteGroup(memberId, groupId, targetId)) - .isInstanceOf(GroupNotFoundException.class) - .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); - } - - @Test - void 그룹장이_아닌_사용자가_그룹원에게_그룹초대를_하면_예외가_발생한다() { - //given - Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; - Member groupOwner = MemberFixture.member(1); - GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(false); - - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); - given(memberRepository.findMemberById(targetId)).willReturn( - Optional.of(MemberFixture.member(2))); - given(groupRepository.findGroupById(groupId)).willReturn(Optional.of(GroupFixture.group())); - given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( - Optional.of(groupOwnerSummaryDto)); - - //when - //then - assertThatThrownBy(() -> groupWriteService.inviteGroup(memberId, groupId, targetId)) - .isInstanceOf(GroupOwnerAuthenticateException.class) - .hasMessageContaining(ErrorCode.GROUP_OWNER_AUTHENTICATE_ERROR.getMessage()); - } - - @Test - void 그룹원은_그룹초대_삭제에서_1을_반환하면_거부할_수_있다() { - //given - Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; - - given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId)).willReturn(1); - - //when - // then - assertThatCode(() -> groupWriteService.rejectRequestGroup(memberId, groupId, targetId)) - .doesNotThrowAnyException(); - } - - @Test - void 그룹원은_그룹초대_삭제에서_0을_반환하면_거부가_실패_한다() { - //given - Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; - - given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId)).willReturn(0); - - //when - // then - assertThatThrownBy(() -> groupWriteService.rejectRequestGroup(memberId, groupId, targetId)) - .isInstanceOf(GroupInviteNotFoundException.class) - .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); - } - - @Test - void 그룹원은_그룹초대를_수락하면_그룹장에게_알림이_전송된다() { - //given - Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; - Member groupMember = MemberFixture.member(1); - - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); - given(groupRepository.findGroupById(groupId)).willReturn( - Optional.of(GroupFixture.group())); - given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId)).willReturn(1); - - //when - groupWriteService.acceptGroupInvite(memberId, groupId, targetId); - - //then - verify(socialNotificationManager, times(1)).sendGroupAcceptMessage(anyString(), anyLong()); - } - - @Test - void 그룹원은_그룹초대를_수락할_때_그룹초대가_존재하지_않으면_예외가_발생한다() { - //given - Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; - Member groupMember = MemberFixture.member(1); - - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); - given(groupRepository.findGroupById(groupId)).willReturn( - Optional.of(GroupFixture.group())); - given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId)).willReturn(0); - - //when - //then - assertThatThrownBy(() -> groupWriteService.acceptGroupInvite(memberId, groupId, targetId)) - .isInstanceOf(GroupInviteNotFoundException.class) - .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); - } - @Test void 존재하지_않는_그룹_아이디로_삭제를_시도하면_예외가_발생한다() { //given @@ -253,7 +104,7 @@ class GroupWriteServiceTest { //when //then - assertThatThrownBy(() -> groupWriteService.deleteGroup(memberId, notExistGroupId)) + assertThatThrownBy(() -> groupCommandService.deleteGroup(memberId, notExistGroupId)) .isExactlyInstanceOf(GroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); } @@ -270,7 +121,7 @@ class GroupWriteServiceTest { //when //then - assertThatThrownBy(() -> groupWriteService.deleteGroup(groupMemberId, groupId)) + assertThatThrownBy(() -> groupCommandService.deleteGroup(groupMemberId, groupId)) .isExactlyInstanceOf(GroupDeleteFailException.class) .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); } @@ -298,7 +149,7 @@ private List notOwnerGroupMember() { //when //then assertThatThrownBy( - () -> groupWriteService.deleteGroup(groupOwnerId, groupMemberExistGroupId)) + () -> groupCommandService.deleteGroup(groupOwnerId, groupMemberExistGroupId)) .isExactlyInstanceOf(GroupDeleteFailException.class) .hasMessageContaining(ErrorCode.GROUP_MEMBER_EXIST_ERROR.getMessage()); } @@ -339,7 +190,7 @@ private List groupMembers() { //when //then assertThatThrownBy( - () -> groupWriteService.deleteGroup(groupOwnerId, groupCapsuleExistGroupId)) + () -> groupCommandService.deleteGroup(groupOwnerId, groupCapsuleExistGroupId)) .isExactlyInstanceOf(GroupDeleteFailException.class) .hasMessageContaining(ErrorCode.GROUP_CAPSULE_EXIST_ERROR.getMessage()); } @@ -368,7 +219,8 @@ private List ownerGroupMember() { //when //then assertThatCode( - () -> groupWriteService.deleteGroup(groupOwnerId, groupId)).doesNotThrowAnyException(); + () -> groupCommandService.deleteGroup(groupOwnerId, + groupId)).doesNotThrowAnyException(); verify(groupRepository, times(1)).delete(any(Group.class)); } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandServiceTest.java new file mode 100644 index 000000000..ae8e1c6c1 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandServiceTest.java @@ -0,0 +1,196 @@ +package site.timecapsulearchive.core.domain.group_member.service; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.springframework.transaction.support.TransactionTemplate; +import site.timecapsulearchive.core.common.dependency.TestTransactionTemplate; +import site.timecapsulearchive.core.common.fixture.domain.GroupFixture; +import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; +import site.timecapsulearchive.core.common.fixture.dto.GroupDtoFixture; +import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; +import site.timecapsulearchive.core.domain.group.repository.GroupRepository; +import site.timecapsulearchive.core.domain.group_member.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.group_member.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.group_member.exception.GroupOwnerAuthenticateException; +import site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; + +public class GroupMemberCommandServiceTest { + + private final MemberRepository memberRepository = mock(MemberRepository.class); + private final GroupRepository groupRepository = mock(GroupRepository.class); + private final MemberGroupRepository memberGroupRepository = mock(MemberGroupRepository.class); + private final GroupInviteRepository groupInviteRepository = mock(GroupInviteRepository.class); + private final SocialNotificationManager socialNotificationManager = mock( + SocialNotificationManager.class); + private final TransactionTemplate transactionTemplate = TestTransactionTemplate.spied(); + + private final GroupMemberCommandService groupQueryService = new GroupMemberCommandService( + memberRepository, + groupRepository, + memberGroupRepository, + groupInviteRepository, + transactionTemplate, + socialNotificationManager + ); + + + @Test + void 그룹장이_그룹원에게_그룹초대를_하면_그룹초대_알림이_요청된다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Long targetId = 2L; + Member groupOwner = MemberFixture.member(1); + GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(true); + + given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); + given(memberRepository.findMemberById(targetId)).willReturn( + Optional.of(MemberFixture.member(2))); + given(groupRepository.findGroupById(groupId)).willReturn(Optional.of(GroupFixture.group())); + given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( + Optional.of(groupOwnerSummaryDto)); + + //when + groupQueryService.inviteGroup(memberId, groupId, targetId); + + //then + verify(socialNotificationManager, times(1)).sendGroupInviteMessage(anyString(), anyString(), + anyList()); + } + + @Test + void 그룹장이_그룹초대를_할_때_존재하지_않은_그룹_아이디_이면_예외가_발생한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Long targetId = 2L; + Member groupOwner = MemberFixture.member(1); + + given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); + given(memberRepository.findMemberById(targetId)).willReturn( + Optional.of(MemberFixture.member(2))); + given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( + Optional.empty()); + + //when + //then + assertThatThrownBy(() -> groupQueryService.inviteGroup(memberId, groupId, targetId)) + .isInstanceOf(GroupNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 그룹장이_아닌_사용자가_그룹원에게_그룹초대를_하면_예외가_발생한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Long targetId = 2L; + Member groupOwner = MemberFixture.member(1); + GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(false); + + given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); + given(memberRepository.findMemberById(targetId)).willReturn( + Optional.of(MemberFixture.member(2))); + given(groupRepository.findGroupById(groupId)).willReturn(Optional.of(GroupFixture.group())); + given(memberGroupRepository.findOwnerInMemberGroup(groupId, memberId)).willReturn( + Optional.of(groupOwnerSummaryDto)); + + //when + //then + assertThatThrownBy(() -> groupQueryService.inviteGroup(memberId, groupId, targetId)) + .isInstanceOf(GroupOwnerAuthenticateException.class) + .hasMessageContaining(ErrorCode.GROUP_OWNER_AUTHENTICATE_ERROR.getMessage()); + } + + @Test + void 그룹원은_그룹초대_삭제에서_1을_반환하면_거부할_수_있다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Long targetId = 2L; + + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(1); + + //when + // then + assertThatCode(() -> groupQueryService.rejectRequestGroup(memberId, groupId, targetId)) + .doesNotThrowAnyException(); + } + + @Test + void 그룹원은_그룹초대_삭제에서_0을_반환하면_거부가_실패_한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Long targetId = 2L; + + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(0); + + //when + // then + assertThatThrownBy( + () -> groupQueryService.rejectRequestGroup(memberId, groupId, targetId)) + .isInstanceOf(GroupInviteNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 그룹원은_그룹초대를_수락하면_그룹장에게_알림이_전송된다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Long targetId = 2L; + Member groupMember = MemberFixture.member(1); + + given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); + given(groupRepository.findGroupById(groupId)).willReturn( + Optional.of(GroupFixture.group())); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(1); + + //when + groupQueryService.acceptGroupInvite(memberId, groupId, targetId); + + //then + verify(socialNotificationManager, times(1)).sendGroupAcceptMessage(anyString(), anyLong()); + } + + @Test + void 그룹원은_그룹초대를_수락할_때_그룹초대가_존재하지_않으면_예외가_발생한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Long targetId = 2L; + Member groupMember = MemberFixture.member(1); + + given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); + given(groupRepository.findGroupById(groupId)).willReturn( + Optional.of(GroupFixture.group())); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(0); + + //when + //then + assertThatThrownBy(() -> groupQueryService.acceptGroupInvite(memberId, groupId, targetId)) + .isInstanceOf(GroupInviteNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); + } + +}