From 42efafbf2b90481b78a87a20c838884b40e3eb19 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Wed, 8 May 2024 15:16:17 +0900 Subject: [PATCH 001/195] =?UTF-8?q?feat=20:=20=ED=94=84=EB=A1=9D=EC=8B=9C?= =?UTF-8?q?=20=ED=8C=A8=ED=84=B4=EC=9C=BC=EB=A1=9C=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/api/GroupApi.java | 13 +-- .../domain/group/api/GroupApiController.java | 18 +++- .../group/service/GroupReadService.java | 20 +++++ .../group/service/GroupReadServiceImpl.java | 53 ++++++++++++ .../domain/group/service/GroupService.java | 84 +------------------ .../group/service/GroupServiceImpl.java | 47 +++++++++++ .../group/service/GroupWriteService.java | 10 +++ .../group/service/GroupWriteServiceImpl.java | 53 ++++++++++++ 8 files changed, 206 insertions(+), 92 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadService.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteService.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java 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 d90e76ef5..e80e56508 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 @@ -181,13 +181,14 @@ ResponseEntity> findGroups( description = "처리 시작" ) }) - @PostMapping(value = "/groups/{group_id}/members/{member_id}/invitation") - ResponseEntity inviteGroup( - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, + ResponseEntity> inviteGroup( + Long memberId, - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true, schema = @Schema()) - @PathVariable("member_id") Long memberId + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) + Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) + Long targetId ); @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 e8a067e35..1f5afbd46 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 @@ -84,7 +84,8 @@ public ResponseEntity> findGroupDetailById( @AuthenticationPrincipal final Long memberId, @PathVariable("group_id") final Long groupId ) { - final GroupDetailDto groupDetailDto = groupService.findGroupDetailByGroupId(memberId, groupId); + final GroupDetailDto groupDetailDto = groupService.findGroupDetailByGroupId(memberId, + groupId); return ResponseEntity.ok( ApiSpec.success( @@ -118,9 +119,20 @@ public ResponseEntity> findGroups( ); } + @PostMapping(value = "/invite/{group_id}/member/{target_id}") @Override - public ResponseEntity inviteGroup(Long groupId, Long memberId) { - return null; + 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.SUCCESS + ) + ); } @Override diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadService.java new file mode 100644 index 000000000..df5824887 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadService.java @@ -0,0 +1,20 @@ +package site.timecapsulearchive.core.domain.group.service; + +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/GroupReadServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java new file mode 100644 index 000000000..fd639f66f --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java @@ -0,0 +1,53 @@ +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 org.springframework.transaction.annotation.Transactional; +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.exception.GroupNotFoundException; +import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepository; +import site.timecapsulearchive.core.domain.group.repository.GroupRepository; + +@Service +@RequiredArgsConstructor +public class GroupReadServiceImpl implements GroupReadService { + + private final GroupQueryRepository groupQueryRepository; + private final GroupRepository groupRepository; + + + @Transactional(readOnly = true) + public Group findGroupById(final Long groupId) { + return groupRepository.findGroupById(groupId) + .orElseThrow(GroupNotFoundException::new); + } + + @Transactional(readOnly = true) + public Slice findGroupsSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + return groupQueryRepository.findGroupsSlice(memberId, size, createdAt); + } + + @Transactional(readOnly = true) + public GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { + final GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupId(groupId) + .orElseThrow(GroupNotFoundException::new); + + final boolean isGroupMember = groupDetailDto.members() + .stream() + .anyMatch(m -> m.memberId().equals(memberId)); + + if (!isGroupMember) { + throw new GroupNotFoundException(); + } + + return groupDetailDto; + } +} 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 index 98b3b1603..5c04d6a01 100644 --- 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 @@ -1,87 +1,5 @@ package site.timecapsulearchive.core.domain.group.service; -import lombok.RequiredArgsConstructor; -import java.time.ZonedDateTime; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; -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.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.entity.MemberGroup; -import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; -import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepository; -import site.timecapsulearchive.core.domain.group.repository.GroupRepository; -import site.timecapsulearchive.core.domain.group.repository.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; +public interface GroupService extends GroupReadService, GroupWriteService { -@Service -@RequiredArgsConstructor -public class GroupService { - - private final GroupRepository groupRepository; - private final MemberRepository memberRepository; - private final MemberGroupRepository memberGroupRepository; - private final TransactionTemplate transactionTemplate; - private final SocialNotificationManager socialNotificationManager; - private final GroupQueryRepository groupQueryRepository; - - public void createGroup(final Long memberId, final GroupCreateDto dto) { - final Member member = memberRepository.findMemberById(memberId) - .orElseThrow(MemberNotFoundException::new); - - final Group group = dto.toEntity(); - - final MemberGroup memberGroup = MemberGroup.createGroupOwner(member, group); - - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - groupRepository.save(group); - memberGroupRepository.save(memberGroup); - } - }); - - socialNotificationManager.sendGroupInviteMessage(member.getNickname(), - dto.groupProfileUrl(), dto.targetIds()); - } - - @Transactional(readOnly = true) - public Group findGroupById(Long groupId) { - return groupRepository.findGroupById(groupId) - .orElseThrow(GroupNotFoundException::new); - } - - @Transactional(readOnly = true) - public Slice findGroupsSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ) { - return groupQueryRepository.findGroupsSlice(memberId, size, createdAt); - } - - @Transactional(readOnly = true) - public GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { - final GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupId(groupId) - .orElseThrow(GroupNotFoundException::new); - - final boolean isGroupMember = groupDetailDto.members() - .stream() - .anyMatch(m -> m.memberId().equals(memberId)); - - if (!isGroupMember) { - throw new GroupNotFoundException(); - } - - return groupDetailDto; - } } 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 new file mode 100644 index 000000000..b765342d7 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupServiceImpl.java @@ -0,0 +1,47 @@ +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; + +@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); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteService.java new file mode 100644 index 000000000..de57f7747 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteService.java @@ -0,0 +1,10 @@ +package site.timecapsulearchive.core.domain.group.service; + +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); +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java new file mode 100644 index 000000000..d7c64718f --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java @@ -0,0 +1,53 @@ +package site.timecapsulearchive.core.domain.group.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; +import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; +import site.timecapsulearchive.core.domain.group.entity.Group; +import site.timecapsulearchive.core.domain.group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group.repository.GroupRepository; +import site.timecapsulearchive.core.domain.group.repository.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 GroupWriteServiceImpl implements GroupWriteService { + + private final GroupRepository groupRepository; + private final MemberRepository memberRepository; + private final MemberGroupRepository memberGroupRepository; + private final TransactionTemplate transactionTemplate; + private final SocialNotificationManager socialNotificationManager; + + public void createGroup(final Long memberId, final GroupCreateDto dto) { + final Member member = memberRepository.findMemberById(memberId) + .orElseThrow(MemberNotFoundException::new); + + final Group group = dto.toEntity(); + + final MemberGroup memberGroup = MemberGroup.createGroupOwner(member, group); + + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + groupRepository.save(group); + memberGroupRepository.save(memberGroup); + } + }); + + socialNotificationManager.sendGroupInviteMessage(member.getNickname(), + dto.groupProfileUrl(), dto.targetIds()); + } + + @Override + public void inviteGroup(Long memberId, Long groupId, Long targetId) { + + } + +} From 4d0dcdaf8ecc48756aa8520ca8d7170581a7af40 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Wed, 8 May 2024 16:11:02 +0900 Subject: [PATCH 002/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=EC=9E=A5=EC=9D=80=20=EA=B7=B8=EB=A3=B9?= =?UTF-8?q?=EC=9B=90=EC=9D=84=20=EC=B4=88=EB=8C=80=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/group/api/GroupApiController.java | 2 +- .../group/data/dto/GroupOwnerSummaryDto.java | 9 ++ .../GroupOwnerAuthenticateException.java | 11 +++ .../repository/GroupQueryRepository.java | 65 +------------ .../repository/GroupQueryRepositoryImpl.java | 91 +++++++++++++++++++ .../group/repository/GroupRepository.java | 2 +- .../MemberGroupQueryRepository.java | 10 ++ .../MemberGroupQueryRepositoryImpl.java | 40 ++++++++ .../repository/MemberGroupRepository.java | 2 +- .../group/service/GroupReadServiceImpl.java | 7 +- .../group/service/GroupWriteServiceImpl.java | 14 ++- .../core/global/error/ErrorCode.java | 1 + .../MemberGroupQueryRepositoryTest.java | 2 +- 13 files changed, 185 insertions(+), 71 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupOwnerSummaryDto.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupOwnerAuthenticateException.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepository.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryImpl.java 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 1f5afbd46..10665e7a8 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 @@ -130,7 +130,7 @@ public ResponseEntity> inviteGroup( return ResponseEntity.ok( ApiSpec.empty( - SuccessCode.SUCCESS + SuccessCode.ACCEPTED ) ); } 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/data/dto/GroupOwnerSummaryDto.java new file mode 100644 index 000000000..3496d3ec8 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupOwnerSummaryDto.java @@ -0,0 +1,9 @@ +package site.timecapsulearchive.core.domain.group.data.dto; + +public record GroupOwnerSummaryDto( + String nickname, + Boolean isOwner, + String groupProfileUrl +) { + +} 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/exception/GroupOwnerAuthenticateException.java new file mode 100644 index 000000000..2c6382b85 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupOwnerAuthenticateException.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 GroupOwnerAuthenticateException extends BusinessException { + + public GroupOwnerAuthenticateException() { + super(ErrorCode.GROUP_OWNER_AUTHENTICATE_ERROR); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index 9b7e5a201..b61fcf46c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -20,72 +20,15 @@ import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; -@Repository -@RequiredArgsConstructor -public class GroupQueryRepository { - private final JPAQueryFactory jpaQueryFactory; +public interface GroupQueryRepository { - public Slice findGroupsSlice( + Slice findGroupsSlice( final Long memberId, final int size, final ZonedDateTime createdAt - ) { - final List groups = jpaQueryFactory - .select( - Projections.constructor( - GroupSummaryDto.class, - group.id, - group.groupName, - group.groupDescription, - group.groupProfileUrl, - group.createdAt, - memberGroup.isOwner - ) - ) - .from(memberGroup) - .join(memberGroup.group, group) - .where(memberGroup.member.id.eq(memberId).and(memberGroup.createdAt.lt(createdAt))) - .limit(size + 1) - .fetch(); + ); - final boolean hasNext = groups.size() > size; - if (hasNext) { - groups.remove(size); - } + Optional findGroupDetailByGroupId(final Long groupId); - return new SliceImpl<>(groups, Pageable.ofSize(size), groups.size() > size); - } - - public Optional findGroupDetailByGroupId(final Long groupId) { - return Optional.ofNullable( - jpaQueryFactory - .selectFrom(group) - .join(memberGroup).on(memberGroup.group.id.eq(group.id)) - .join(member).on(member.id.eq(memberGroup.member.id)) - .where(group.id.eq(groupId)) - .transform( - groupBy(group.id).as( - Projections.constructor( - GroupDetailDto.class, - group.groupName, - group.groupDescription, - group.groupProfileUrl, - group.createdAt, - list( - Projections.constructor( - GroupMemberDto.class, - member.id, - member.profileUrl, - member.nickname, - member.tag, - memberGroup.isOwner - ) - ) - ) - ) - ) - .get(groupId) - ); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java new file mode 100644 index 000000000..56f49b38e --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -0,0 +1,91 @@ +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.member.entity.QMember.member; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; + +@Repository +@RequiredArgsConstructor +public class GroupQueryRepositoryImpl implements GroupQueryRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public Slice findGroupsSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + final List groups = jpaQueryFactory + .select( + Projections.constructor( + GroupSummaryDto.class, + group.id, + group.groupName, + group.groupDescription, + group.groupProfileUrl, + group.createdAt, + memberGroup.isOwner + ) + ) + .from(memberGroup) + .join(memberGroup.group, group) + .where(memberGroup.member.id.eq(memberId).and(memberGroup.createdAt.lt(createdAt))) + .limit(size + 1) + .fetch(); + + final boolean hasNext = groups.size() > size; + if (hasNext) { + groups.remove(size); + } + + return new SliceImpl<>(groups, Pageable.ofSize(size), groups.size() > size); + } + + public Optional findGroupDetailByGroupId(final Long groupId) { + return Optional.ofNullable( + jpaQueryFactory + .selectFrom(group) + .join(memberGroup).on(memberGroup.group.id.eq(group.id)) + .join(member).on(member.id.eq(memberGroup.member.id)) + .where(group.id.eq(groupId)) + .transform( + groupBy(group.id).as( + Projections.constructor( + GroupDetailDto.class, + group.groupName, + group.groupDescription, + group.groupProfileUrl, + group.createdAt, + list( + Projections.constructor( + GroupMemberDto.class, + member.id, + member.profileUrl, + member.nickname, + member.tag, + memberGroup.isOwner + ) + ) + ) + ) + ) + .get(groupId) + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java index 383258106..47b09d6f6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java @@ -4,7 +4,7 @@ import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.group.entity.Group; -public interface GroupRepository extends Repository { +public interface GroupRepository extends Repository, GroupQueryRepository { void save(Group group); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepository.java new file mode 100644 index 000000000..e0cca98db --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepository.java @@ -0,0 +1,10 @@ +package site.timecapsulearchive.core.domain.group.repository; + +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/repository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryImpl.java new file mode 100644 index 000000000..ac36d55a0 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryImpl.java @@ -0,0 +1,40 @@ +package site.timecapsulearchive.core.domain.group.repository; + +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.member.entity.QMember.member; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; + +@Repository +@RequiredArgsConstructor +public class MemberGroupQueryRepositoryImpl implements MemberGroupQueryRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public Optional findOwnerInMemberGroup(final Long groupId, + final Long memberId) { + return Optional.ofNullable(jpaQueryFactory + .select( + Projections.constructor( + GroupOwnerSummaryDto.class, + member.nickname, + memberGroup.isOwner, + group.groupProfileUrl + ) + ) + .from(memberGroup) + .join(memberGroup.member, member) + .join(memberGroup.group, group) + .where(memberGroup.group.id.eq(groupId) + .and(memberGroup.member.id.eq(memberId))) + .fetchFirst() + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupRepository.java index b5e46ade4..bb5c4cd12 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupRepository.java @@ -3,7 +3,7 @@ import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.group.entity.MemberGroup; -public interface MemberGroupRepository extends Repository { +public interface MemberGroupRepository extends Repository, MemberGroupQueryRepository{ void save(MemberGroup memberGroup); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java index fd639f66f..65808e608 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java @@ -9,17 +9,14 @@ 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.GroupQueryRepository; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; @Service @RequiredArgsConstructor public class GroupReadServiceImpl implements GroupReadService { - private final GroupQueryRepository groupQueryRepository; private final GroupRepository groupRepository; - @Transactional(readOnly = true) public Group findGroupById(final Long groupId) { return groupRepository.findGroupById(groupId) @@ -32,12 +29,12 @@ public Slice findGroupsSlice( final int size, final ZonedDateTime createdAt ) { - return groupQueryRepository.findGroupsSlice(memberId, size, createdAt); + return groupRepository.findGroupsSlice(memberId, size, createdAt); } @Transactional(readOnly = true) public GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { - final GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupId(groupId) + final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupId(groupId) .orElseThrow(GroupNotFoundException::new); final boolean isGroupMember = groupDetailDto.members() diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java index d7c64718f..f714d5432 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java @@ -1,13 +1,17 @@ package site.timecapsulearchive.core.domain.group.service; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; 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.GroupNotFoundException; +import site.timecapsulearchive.core.domain.group.exception.GroupOwnerAuthenticateException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.group.repository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member.entity.Member; @@ -46,8 +50,16 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Override - public void inviteGroup(Long memberId, Long groupId, Long targetId) { + public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { + final GroupOwnerSummaryDto summaryDto = memberGroupRepository.findOwnerInMemberGroup( + groupId, memberId).orElseThrow(GroupNotFoundException::new); + if (!summaryDto.isOwner()) { + throw new GroupOwnerAuthenticateException(); + } + + socialNotificationManager.sendGroupInviteMessage(summaryDto.nickname(), + summaryDto.groupProfileUrl(), List.of(targetId)); } } 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 d1f5dffa2..d7a3667b1 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 @@ -60,6 +60,7 @@ public enum ErrorCode { //group GROUP_CREATE_ERROR(400, "GROUP-001", "그룹 생성에 실패하였습니다."), GROUP_NOT_FOUND_ERROR(404, "GROUP-002", "그룹을 찾을 수 없습니다"), + GROUP_OWNER_AUTHENTICATE_ERROR(400, "GROUP-003", "그룹장이 아닙니다."), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), 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 bb5f408de..b2356e25b 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 @@ -40,7 +40,7 @@ class MemberGroupQueryRepositoryTest extends RepositoryTest { private Long ownerGroupId; MemberGroupQueryRepositoryTest(JPAQueryFactory jpaQueryFactory) { - this.groupQueryRepository = new GroupQueryRepository(jpaQueryFactory); + this.groupQueryRepository = new GroupQueryRepositoryImpl(jpaQueryFactory); } @Transactional From b0b03dc11e85ac4aff8668056ee398dd94582cea Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Wed, 8 May 2024 17:36:53 +0900 Subject: [PATCH 003/195] =?UTF-8?q?refact:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 그룹 초대 엔티티 수정 - 그룹 생성 시 그룹 초대 쿼리 추가 --- .../FriendInviteQueryRepository.java | 8 +++- .../core/domain/group/entity/GroupInvite.java | 17 ++++++- .../GroupInviteQueryRepository.java | 8 ++++ .../GroupInviteQueryRepositoryImpl.java | 48 +++++++++++++++++++ .../repository/GroupInviteRepository.java | 6 ++- .../group/service/GroupWriteServiceImpl.java | 37 ++++++++++---- .../core/domain/member/entity/Member.java | 3 -- .../db/migration/V25__group_invite_update.sql | 20 ++++++++ 8 files changed, 130 insertions(+), 17 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepository.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepositoryImpl.java create mode 100644 backend/core/src/main/resources/db/migration/V25__group_invite_update.sql diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepository.java index 640c2077d..3e6859eea 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepository.java @@ -2,7 +2,9 @@ import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.Timestamp; import java.sql.Types; +import java.time.ZonedDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.jdbc.core.BatchPreparedStatementSetter; @@ -19,8 +21,8 @@ public void bulkSave(final Long ownerId, final List friendIds) { jdbcTemplate.batchUpdate( """ INSERT INTO friend_invite ( - friend_invite_id, owner_id, friend_id - ) values (?, ?, ?) + friend_invite_id, owner_id, friend_id, created_at, updated_at + ) values (?, ?, ?, ?, ?) """, new BatchPreparedStatementSetter() { @@ -30,6 +32,8 @@ public void setValues(final PreparedStatement ps, final int i) throws SQLExcepti ps.setNull(1, Types.BIGINT); ps.setLong(2, ownerId); ps.setLong(3, friendId); + ps.setTimestamp(4, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setTimestamp(5, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); } @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 41c184b35..434e245ba 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 @@ -27,7 +27,20 @@ public class GroupInvite extends BaseEntity { private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) - private Member member; + @JoinColumn(name = "group_owner_id", nullable = false) + private Member groupOwner; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "group_member_id", nullable = false) + private Member groupMember; + + private GroupInvite(Member groupOwner, Member groupMember) { + this.groupOwner = groupOwner; + this.groupMember = groupMember; + } + + public static GroupInvite createOf(Member groupOwner, Member groupMember) { + return new GroupInvite(groupOwner, groupMember); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepository.java new file mode 100644 index 000000000..e9abf2cf4 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepository.java @@ -0,0 +1,8 @@ +package site.timecapsulearchive.core.domain.group.repository; + +import java.util.List; + +public interface GroupInviteQueryRepository { + + void bulkSave(final Long groupOwnerId, final List groupMemberIds); +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepositoryImpl.java new file mode 100644 index 000000000..3fdf1ef9c --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepositoryImpl.java @@ -0,0 +1,48 @@ +package site.timecapsulearchive.core.domain.group.repository; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.ZonedDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepository { + + private final JdbcTemplate jdbcTemplate; + + @Override + public void bulkSave(Long groupOwnerId, List groupMemberIds) { + jdbcTemplate.batchUpdate( + """ + INSERT INTO group_invite ( + group_invite_id, group_owner_id, group_member_id, created_at, updated_at + ) values (?, ?, ?, ?, ?) + """, + new BatchPreparedStatementSetter() { + + @Override + public void setValues(final PreparedStatement ps, final int i) throws SQLException { + final Long groupMember = groupMemberIds.get(i); + ps.setNull(1, Types.BIGINT); + ps.setLong(2, groupOwnerId); + ps.setLong(3, groupMember); + ps.setTimestamp(4, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setTimestamp(5, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + } + + @Override + public int getBatchSize() { + return groupMemberIds.size(); + } + } + ); + } + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteRepository.java index edd27e243..585a107ca 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteRepository.java @@ -1,9 +1,11 @@ package site.timecapsulearchive.core.domain.group.repository; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.group.entity.GroupInvite; -public interface GroupInviteRepository extends JpaRepository { +public interface GroupInviteRepository extends Repository, + GroupInviteQueryRepository { + void save(GroupInvite groupInvite); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java index f714d5432..0f43f8ea0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java @@ -9,9 +9,11 @@ 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.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupOwnerAuthenticateException; +import site.timecapsulearchive.core.domain.group.repository.GroupInviteRepository; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.group.repository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member.entity.Member; @@ -23,9 +25,10 @@ @RequiredArgsConstructor public class GroupWriteServiceImpl implements GroupWriteService { - private final GroupRepository groupRepository; private final MemberRepository memberRepository; + private final GroupRepository groupRepository; private final MemberGroupRepository memberGroupRepository; + private final GroupInviteRepository groupInviteRepository; private final TransactionTemplate transactionTemplate; private final SocialNotificationManager socialNotificationManager; @@ -42,6 +45,7 @@ public void createGroup(final Long memberId, final GroupCreateDto dto) { protected void doInTransactionWithoutResult(TransactionStatus status) { groupRepository.save(group); memberGroupRepository.save(memberGroup); + groupInviteRepository.bulkSave(memberId, dto.targetIds()); } }); @@ -51,15 +55,32 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Override public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { - final GroupOwnerSummaryDto summaryDto = memberGroupRepository.findOwnerInMemberGroup( - groupId, memberId).orElseThrow(GroupNotFoundException::new); + final Member groupOwner = memberRepository.findMemberById(memberId).orElseThrow( + MemberNotFoundException::new); + + final Member groupMember = memberRepository.findMemberById(targetId).orElseThrow( + MemberNotFoundException::new); + + final GroupInvite groupInvite = GroupInvite.createOf(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.isOwner()) { - throw new GroupOwnerAuthenticateException(); - } + if (!summaryDto[0].isOwner()) { + throw new GroupOwnerAuthenticateException(); + } + + groupInviteRepository.save(groupInvite); + } + }); - socialNotificationManager.sendGroupInviteMessage(summaryDto.nickname(), - summaryDto.groupProfileUrl(), List.of(targetId)); + socialNotificationManager.sendGroupInviteMessage(summaryDto[0].nickname(), + summaryDto[0].groupProfileUrl(), List.of(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 730084363..17abf213c 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 @@ -77,9 +77,6 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private List capsules; - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List groupInvites; - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private List groups; diff --git a/backend/core/src/main/resources/db/migration/V25__group_invite_update.sql b/backend/core/src/main/resources/db/migration/V25__group_invite_update.sql new file mode 100644 index 000000000..729e12a70 --- /dev/null +++ b/backend/core/src/main/resources/db/migration/V25__group_invite_update.sql @@ -0,0 +1,20 @@ +alter table group_invite + drop foreign key fk_group_invite_member_id; + +alter table group_invite + drop column member_id; + + +ALTER TABLE group_invite + ADD COLUMN group_owner_id BIGINT; +ALTER TABLE group_invite + ADD COLUMN group_member_id BIGINT; + +ALTER TABLE group_invite + ADD CONSTRAINT fk_group_invite_group_owner_id FOREIGN KEY (group_owner_id) REFERENCES member (member_id); + +ALTER TABLE group_invite + ADD CONSTRAINT fk_group_invite_group_member_id FOREIGN KEY (group_member_id) REFERENCES member (member_id); + +ALTER TABLE group_invite + ADD CONSTRAINT unique_owner_member_pair UNIQUE (group_owner_id, group_member_id); From 53b255d0d7ccc7e63ef7840f164ccff94c02886e Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Wed, 8 May 2024 19:09:18 +0900 Subject: [PATCH 004/195] =?UTF-8?q?chore=20:=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NearbyARCapsuleSummaryResponse.java | 1 + .../data/dto/MySecreteCapsuleDto.java | 1 - .../domain/group/data/dto/GroupDetailDto.java | 1 - .../repository/GroupQueryRepository.java | 34 ------------------- .../GroupInviteQueryRepository.java | 2 +- .../GroupInviteQueryRepositoryImpl.java | 2 +- .../GroupInviteRepository.java | 2 +- .../groupRepository/GroupQueryRepository.java | 20 +++++++++++ .../GroupQueryRepositoryImpl.java | 2 +- .../GroupRepository.java | 2 +- .../MemberGroupQueryRepository.java | 2 +- .../MemberGroupQueryRepositoryImpl.java | 2 +- .../MemberGroupRepository.java | 5 +-- .../domain/group/service/GroupService.java | 3 ++ .../group/service/GroupServiceImpl.java | 2 ++ .../service/{ => read}/GroupReadService.java | 2 +- .../{ => read}/GroupReadServiceImpl.java | 4 +-- .../{ => write}/GroupWriteService.java | 2 +- .../{ => write}/GroupWriteServiceImpl.java | 8 ++--- .../core/domain/member/entity/Member.java | 1 - .../MemberGroupQueryRepositoryTest.java | 2 ++ 21 files changed, 46 insertions(+), 54 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => groupInviteRepository}/GroupInviteQueryRepository.java (64%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => groupInviteRepository}/GroupInviteQueryRepositoryImpl.java (95%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => groupInviteRepository}/GroupInviteRepository.java (76%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepository.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => groupRepository}/GroupQueryRepositoryImpl.java (97%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => groupRepository}/GroupRepository.java (80%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => memberGroupRepository}/MemberGroupQueryRepository.java (74%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => memberGroupRepository}/MemberGroupQueryRepositoryImpl.java (94%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/{ => memberGroupRepository}/MemberGroupRepository.java (64%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/{ => read}/GroupReadService.java (90%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/{ => read}/GroupReadServiceImpl.java (91%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/{ => write}/GroupWriteService.java (80%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/{ => write}/GroupWriteServiceImpl.java (91%) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/data/response/NearbyARCapsuleSummaryResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/data/response/NearbyARCapsuleSummaryResponse.java index 66eaa0398..fe4777a1d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/data/response/NearbyARCapsuleSummaryResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/data/response/NearbyARCapsuleSummaryResponse.java @@ -34,6 +34,7 @@ public record NearbyARCapsuleSummaryResponse( @Schema(description = "캡슐 타입") CapsuleType capsuleType ) { + public NearbyARCapsuleSummaryResponse { if (dueDate != null) { dueDate = dueDate.withZoneSameInstant(ResponseMappingConstant.ZONE_ID); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/secret_capsule/data/dto/MySecreteCapsuleDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/secret_capsule/data/dto/MySecreteCapsuleDto.java index 9b3c37704..19d661802 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/secret_capsule/data/dto/MySecreteCapsuleDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/secret_capsule/data/dto/MySecreteCapsuleDto.java @@ -4,7 +4,6 @@ import java.util.function.Function; import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; import site.timecapsulearchive.core.domain.capsule.secret_capsule.data.response.MySecreteCapsuleResponse; -import site.timecapsulearchive.core.global.common.response.ResponseMappingConstant; public record MySecreteCapsuleDto( Long capsuleId, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java index c45a8a46e..d97da178e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java @@ -5,7 +5,6 @@ import java.util.function.Function; import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; import site.timecapsulearchive.core.domain.group.data.response.GroupMemberResponse; -import site.timecapsulearchive.core.domain.group.data.response.GroupMemberSummaryResponse; public record GroupDetailDto( String groupName, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java deleted file mode 100644 index b61fcf46c..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ /dev/null @@ -1,34 +0,0 @@ -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.member.entity.QMember.member; - -import com.querydsl.core.types.Projections; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import org.springframework.stereotype.Repository; -import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; - - -public interface GroupQueryRepository { - - Slice findGroupsSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ); - - Optional findGroupDetailByGroupId(final Long groupId); - -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java similarity index 64% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java index e9abf2cf4..757acc930 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; import java.util.List; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java similarity index 95% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 3fdf1ef9c..8d71332c7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; import java.sql.PreparedStatement; import java.sql.SQLException; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java similarity index 76% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java index 585a107ca..87d2cce84 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupInviteRepository/GroupInviteRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.groupInviteRepository; import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.group.entity.GroupInvite; 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/groupRepository/GroupQueryRepository.java new file mode 100644 index 000000000..0ce6327a9 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepository.java @@ -0,0 +1,20 @@ +package site.timecapsulearchive.core.domain.group.repository.groupRepository; + +import java.time.ZonedDateTime; +import java.util.Optional; +import org.springframework.data.domain.Slice; +import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; + + +public interface GroupQueryRepository { + + Slice findGroupsSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ); + + Optional findGroupDetailByGroupId(final Long groupId); + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepositoryImpl.java similarity index 97% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepositoryImpl.java index 56f49b38e..21ff3e10b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupQueryRepositoryImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.groupRepository; import static com.querydsl.core.group.GroupBy.groupBy; import static com.querydsl.core.group.GroupBy.list; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java similarity index 80% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java index 47b09d6f6..24eb4c0c7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/groupRepository/GroupRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.groupRepository; import java.util.Optional; import org.springframework.data.repository.Repository; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepository.java similarity index 74% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepository.java index e0cca98db..c39ef58b2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.memberGroupRepository; import java.util.Optional; import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java similarity index 94% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index ac36d55a0..6e240515a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.memberGroupRepository; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; import static site.timecapsulearchive.core.domain.group.entity.QMemberGroup.memberGroup; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java similarity index 64% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java index bb5c4cd12..d5ded67da 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/memberGroupRepository/MemberGroupRepository.java @@ -1,9 +1,10 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.group.repository.memberGroupRepository; import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.group.entity.MemberGroup; -public interface MemberGroupRepository extends Repository, MemberGroupQueryRepository{ +public interface MemberGroupRepository extends Repository, + MemberGroupQueryRepository { void save(MemberGroup memberGroup); } 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 index 5c04d6a01..5fa63bedf 100644 --- 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 @@ -1,5 +1,8 @@ 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 index b765342d7..9d6342b83 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 @@ -8,6 +8,8 @@ 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 diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadService.java similarity index 90% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadService.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadService.java index df5824887..635b1dbb1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadService.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.service; +package site.timecapsulearchive.core.domain.group.service.read; import java.time.ZonedDateTime; import org.springframework.data.domain.Slice; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadServiceImpl.java similarity index 91% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadServiceImpl.java index 65808e608..2d833a0ca 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupReadServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/read/GroupReadServiceImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.service; +package site.timecapsulearchive.core.domain.group.service.read; import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; @@ -9,7 +9,7 @@ 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; +import site.timecapsulearchive.core.domain.group.repository.groupRepository.GroupRepository; @Service @RequiredArgsConstructor diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java similarity index 80% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteService.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java index de57f7747..891dc13d9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteService.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.service; +package site.timecapsulearchive.core.domain.group.service.write; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java similarity index 91% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java index 0f43f8ea0..f2da391e7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/write/GroupWriteServiceImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.service; +package site.timecapsulearchive.core.domain.group.service.write; import java.util.List; import lombok.RequiredArgsConstructor; @@ -13,9 +13,9 @@ import site.timecapsulearchive.core.domain.group.entity.MemberGroup; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.exception.GroupOwnerAuthenticateException; -import site.timecapsulearchive.core.domain.group.repository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.group.repository.GroupRepository; -import site.timecapsulearchive.core.domain.group.repository.MemberGroupRepository; +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.member.entity.Member; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; 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 17abf213c..a99bd8fff 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,6 @@ 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.GroupInvite; import site.timecapsulearchive.core.domain.group.entity.MemberGroup; import site.timecapsulearchive.core.domain.history.entity.History; import site.timecapsulearchive.core.global.entity.BaseEntity; 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 b2356e25b..08f0234d4 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 @@ -26,6 +26,8 @@ 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.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) From 4366d2638fe4bd6e371096e3044288f519d327a7 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 10 May 2024 01:27:42 +0900 Subject: [PATCH 005/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EA=B1=B0=EB=B6=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/friend/service/FriendService.java | 4 ++-- .../core/domain/group/api/GroupApi.java | 10 ++++------ .../core/domain/group/api/GroupApiController.java | 12 +++++++++--- .../exception/GroupInviteNotFoundException.java | 11 +++++++++++ .../GroupInviteRepository.java | 2 ++ .../core/domain/group/service/GroupServiceImpl.java | 5 +++++ .../group/service/read/GroupReadServiceImpl.java | 4 +--- .../group/service/write/GroupWriteService.java | 2 ++ .../group/service/write/GroupWriteServiceImpl.java | 13 ++++++++++++- .../core/global/error/ErrorCode.java | 1 + 10 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupInviteNotFoundException.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 913700af2..82450f8e1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -114,7 +114,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Transactional - public void denyRequestFriend(Long memberId, Long friendId) { + public void denyRequestFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); final FriendInvite friendInvite = friendInviteRepository .findFriendInviteByOwnerIdAndFriendId(memberId, friendId).orElseThrow( @@ -127,7 +127,7 @@ public void denyRequestFriend(Long memberId, Long friendId) { public void deleteFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); - List memberFriends = memberFriendRepository + final List memberFriends = memberFriendRepository .findMemberFriendByOwnerIdAndFriendId(memberId, friendId); memberFriends.forEach(memberFriendRepository::delete); 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 e80e56508..f78fab9d9 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 @@ -111,13 +111,11 @@ ResponseEntity deleteGroupMember( description = "처리 완료" ) }) - @PostMapping(value = "/groups/{group_id}/members/{member_id}/deny-invitation") - ResponseEntity denyGroupInvitation( - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, + ResponseEntity> rejectGroupInvitation( + Long memberId, - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true, schema = @Schema()) - @PathVariable("member_id") Long memberId + @Parameter(in = ParameterIn.PATH, description = "그룹 초대 대상 아이디", required = true) + Long groupOwnerId ); @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 10665e7a8..c61f31e46 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 @@ -6,6 +6,7 @@ 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; @@ -70,9 +71,14 @@ public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { return null; } - @Override - public ResponseEntity denyGroupInvitation(Long groupId, Long memberId) { - return null; + @DeleteMapping(value = "/reject/{group_owner_id}") + public ResponseEntity> rejectGroupInvitation( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_owner_id") final Long groupOwnerId) { + + groupService.denyRequestGroup(memberId, groupOwnerId); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } @GetMapping( 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/exception/GroupInviteNotFoundException.java new file mode 100644 index 000000000..39ad8bb61 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupInviteNotFoundException.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 GroupInviteNotFoundException extends BusinessException { + + public GroupInviteNotFoundException() { + super(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR); + } +} 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 87d2cce84..e18663d1a 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 @@ -7,5 +7,7 @@ public interface GroupInviteRepository extends Repository, GroupInviteQueryRepository { void save(GroupInvite groupInvite); + + int deleteGroupInviteByGroupOwnerIdAndGroupMemberId(Long groupOwnerId, Long groupMemberId); } 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 9d6342b83..359f2ddec 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 @@ -46,4 +46,9 @@ public void createGroup(final Long memberId, final GroupCreateDto dto) { public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { groupWriteService.inviteGroup(memberId, groupId, targetId); } + + @Override + public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { + groupWriteService.denyRequestGroup(groupMemberId, groupOwnerId); + } } 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/read/GroupReadServiceImpl.java index 2d833a0ca..d3066d287 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/read/GroupReadServiceImpl.java @@ -13,17 +13,16 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class GroupReadServiceImpl implements GroupReadService { private final GroupRepository groupRepository; - @Transactional(readOnly = true) public Group findGroupById(final Long groupId) { return groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); } - @Transactional(readOnly = true) public Slice findGroupsSlice( final Long memberId, final int size, @@ -32,7 +31,6 @@ public Slice findGroupsSlice( return groupRepository.findGroupsSlice(memberId, size, createdAt); } - @Transactional(readOnly = true) public GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupId(groupId) .orElseThrow(GroupNotFoundException::new); 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 891dc13d9..c4ab95475 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 @@ -7,4 +7,6 @@ public interface GroupWriteService { void createGroup(final Long memberId, final GroupCreateDto dto); void inviteGroup(final Long memberId, final Long groupId, final Long targetId); + + void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId); } 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 f2da391e7..c1eee618a 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 @@ -4,6 +4,7 @@ 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.data.dto.GroupCreateDto; @@ -11,6 +12,7 @@ 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.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; @@ -53,7 +55,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { dto.groupProfileUrl(), dto.targetIds()); } - @Override public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { final Member groupOwner = memberRepository.findMemberById(memberId).orElseThrow( MemberNotFoundException::new); @@ -83,4 +84,14 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { summaryDto[0].groupProfileUrl(), List.of(targetId)); } + @Transactional + public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { + final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + groupOwnerId, groupMemberId); + + if (isDenyRequest != 1) { + throw new GroupInviteNotFoundException(); + } + } + } 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 d7a3667b1..434fbf85e 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 @@ -61,6 +61,7 @@ public enum ErrorCode { GROUP_CREATE_ERROR(400, "GROUP-001", "그룹 생성에 실패하였습니다."), GROUP_NOT_FOUND_ERROR(404, "GROUP-002", "그룹을 찾을 수 없습니다"), GROUP_OWNER_AUTHENTICATE_ERROR(400, "GROUP-003", "그룹장이 아닙니다."), + GROUP_INVITATION_NOT_FOUND_ERROR(404, "GROUP-004", "그룹 초대를 찾을 수 없습니다"), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), From b1c9515c12f0ba40c4251e845da4db4e14bf1a0b Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 10 May 2024 01:35:18 +0900 Subject: [PATCH 006/195] =?UTF-8?q?refact=20:=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EA=B1=B0=EC=A0=88=20=EC=BF=BC=EB=A6=AC=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 - 기존 delete는 삭제 시 조회 쿼리 한번 더 발생함 --- .../repository/FriendInviteRepository.java | 2 +- .../domain/friend/service/FriendService.java | 20 ++++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java index c7c94a40a..df8643a77 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java @@ -24,7 +24,7 @@ List findFriendInviteWithMembersByOwnerIdAndFriendId( Optional findFriendInviteByOwnerIdAndFriendId(Long memberId, Long targetId); - void deleteFriendInviteById(Long id); + int deleteFriendInviteByOwnerIdAndFriendId(Long memberId, Long targetId); void delete(FriendInvite friendInvite); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 82450f8e1..b070484a4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -44,7 +44,6 @@ public class FriendService { private final TransactionTemplate transactionTemplate; public FriendReqStatusResponse requestFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); validateTwoWayInvite(memberId, friendId); final Member owner = memberRepository.findMemberById(memberId).orElseThrow( @@ -67,12 +66,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { return FriendReqStatusResponse.success(); } - private void validateFriendDuplicateId(final Long memberId, final Long friendId) { - if (memberId.equals(friendId)) { - throw new FriendDuplicateIdException(); - } - } - private void validateTwoWayInvite(final Long memberId, final Long friendId) { final Optional friendInvite = friendInviteRepository.findFriendInviteByOwnerIdAndFriendId( friendId, memberId); @@ -84,8 +77,6 @@ private void validateTwoWayInvite(final Long memberId, final Long friendId) { public void acceptFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); - final String[] ownerNickname = new String[1]; transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -115,18 +106,15 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void denyRequestFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); - final FriendInvite friendInvite = friendInviteRepository - .findFriendInviteByOwnerIdAndFriendId(memberId, friendId).orElseThrow( - FriendNotFoundException::new); + int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, memberId); - friendInviteRepository.deleteFriendInviteById(friendInvite.getId()); + if (isDenyRequest != 1) { + throw new FriendTwoWayInviteException(); + } } @Transactional public void deleteFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); - final List memberFriends = memberFriendRepository .findMemberFriendByOwnerIdAndFriendId(memberId, friendId); From 731a01b66e64f6c7de0a5d3e91b6ef66ab54fd49 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 10 May 2024 01:37:38 +0900 Subject: [PATCH 007/195] =?UTF-8?q?refact=20:=20=EB=B9=84=EB=B0=80=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EC=98=A4=ED=94=88=20=EC=BF=BC=EB=A6=AC=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 - 쿼리 실행 실패 시 예외 처리 추가 --- .../generic_capsule/repository/CapsuleRepository.java | 2 +- .../capsule/generic_capsule/service/CapsuleService.java | 6 +++++- .../core/domain/friend/service/FriendService.java | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java index 4f5378f46..03908bba4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java @@ -19,7 +19,7 @@ Optional findCapsuleByMemberIdAndCapsuleId( @Modifying(clearAutomatically = true) @Query("UPDATE Capsule c SET c.isOpened = true WHERE c.id = :capsuleId and c.member.id = :memberId") - void updateIsOpenedTrue( + int updateIsOpenedTrue( @Param("memberId") Long memberId, @Param("capsuleId") Long capsuleId ); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java index f1414845b..150af31c1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java @@ -74,7 +74,11 @@ public Capsule findCapsuleByMemberIdAndCapsuleId(final Long memberId, final Long @Transactional public void updateIsOpenedTrue(final Long memberId, final Long capsuleId) { - capsuleRepository.updateIsOpenedTrue(memberId, capsuleId); + int isOpenedTrue = capsuleRepository.updateIsOpenedTrue(memberId, capsuleId); + + if (isOpenedTrue != 1) { + throw new CapsuleNotFondException(); + } } @Transactional diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index b070484a4..618e2d645 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -17,7 +17,6 @@ import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; -import site.timecapsulearchive.core.domain.friend.exception.FriendDuplicateIdException; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendTwoWayInviteException; @@ -106,7 +105,8 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void denyRequestFriend(final Long memberId, final Long friendId) { - int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, memberId); + int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, + memberId); if (isDenyRequest != 1) { throw new FriendTwoWayInviteException(); From 2521e9ccfabfb39d09e2b931aa8061d8d7b3a012 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 12 May 2024 01:35:18 +0900 Subject: [PATCH 008/195] =?UTF-8?q?feat=20:=20memberId,=20FriendId=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 필요 없는줄 알았다가 다시 추가 --- .../core/domain/friend/service/FriendService.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 618e2d645..1c57d6eb2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -17,6 +17,7 @@ import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; +import site.timecapsulearchive.core.domain.friend.exception.FriendDuplicateIdException; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendTwoWayInviteException; @@ -43,6 +44,7 @@ public class FriendService { private final TransactionTemplate transactionTemplate; public FriendReqStatusResponse requestFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); validateTwoWayInvite(memberId, friendId); final Member owner = memberRepository.findMemberById(memberId).orElseThrow( @@ -65,6 +67,12 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { return FriendReqStatusResponse.success(); } + private void validateFriendDuplicateId(final Long memberId, final Long friendId) { + if (memberId.equals(friendId)) { + throw new FriendDuplicateIdException(); + } + } + private void validateTwoWayInvite(final Long memberId, final Long friendId) { final Optional friendInvite = friendInviteRepository.findFriendInviteByOwnerIdAndFriendId( friendId, memberId); @@ -76,6 +84,8 @@ private void validateTwoWayInvite(final Long memberId, final Long friendId) { public void acceptFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); + final String[] ownerNickname = new String[1]; transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -105,6 +115,8 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void denyRequestFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); + int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, memberId); @@ -115,6 +127,8 @@ public void denyRequestFriend(final Long memberId, final Long friendId) { @Transactional public void deleteFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); + final List memberFriends = memberFriendRepository .findMemberFriendByOwnerIdAndFriendId(memberId, friendId); From 1fbd27cdf7168eec0f68a36151326f02d611a17c Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 12 May 2024 03:04:11 +0900 Subject: [PATCH 009/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=88=98=EB=9D=BD=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/api/GroupApi.java | 14 +++++----- .../domain/group/api/GroupApiController.java | 23 +++++++++++++--- .../core/domain/group/entity/MemberGroup.java | 4 +++ .../group/service/GroupServiceImpl.java | 5 ++++ .../service/write/GroupWriteService.java | 3 +++ .../service/write/GroupWriteServiceImpl.java | 24 +++++++++++++++++ .../member/repository/MemberRepository.java | 2 -- .../rabbitmq/RabbitFailedComponentConfig.java | 25 +++++++++++++++-- .../rabbitmq/RabbitmqComponentConstants.java | 12 ++++++--- .../config/rabbitmq/RabbitmqConfig.java | 25 +++++++++++++++-- .../data/dto/GroupAcceptNotificationDto.java | 27 +++++++++++++++++++ .../data/dto/NotificationRequestMessage.java | 3 ++- .../manager/SocialNotificationManager.java | 16 +++++++++-- 13 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java 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 f78fab9d9..219fc6299 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 @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; 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; @@ -34,13 +33,14 @@ public interface GroupApi { description = "처리 완료" ) }) - @PostMapping(value = "/groups/{group_id}/members/{member_id}/accept-invitation") - ResponseEntity acceptGroupInvitation( - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, + ResponseEntity> acceptGroupInvitation( + Long memberId, - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true, schema = @Schema()) - @PathVariable("member_id") Long memberId + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) + Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) + Long targetId ); @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 c61f31e46..b6ffad11d 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 @@ -37,9 +37,20 @@ public class GroupApiController implements GroupApi { private final S3UrlGenerator s3UrlGenerator; private final S3PreSignedUrlManager s3PreSignedUrlManager; + @PostMapping(value = "/accept/{group_id}/member/{target_id}") @Override - public ResponseEntity acceptGroupInvitation(Long groupId, Long memberId) { - return null; + 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 @@ -56,7 +67,7 @@ public ResponseEntity> createGroup( return ResponseEntity.ok( ApiSpec.empty( - SuccessCode.SUCCESS + SuccessCode.ACCEPTED ) ); } @@ -78,7 +89,11 @@ public ResponseEntity> rejectGroupInvitation( groupService.denyRequestGroup(memberId, groupOwnerId); - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); } @GetMapping( 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/entity/MemberGroup.java index d9c4f5192..67c37404c 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/entity/MemberGroup.java @@ -46,4 +46,8 @@ private MemberGroup(Boolean isOwner, Member member, Group group) { public static MemberGroup createGroupOwner(Member member, Group group) { return new MemberGroup(true, member, group); } + + public static MemberGroup createGroupMember(Member member, Group group) { + return new MemberGroup(false, member, group); + } } 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 359f2ddec..f993f3e97 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 @@ -51,4 +51,9 @@ public void inviteGroup(final Long memberId, final Long groupId, final Long targ public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { groupWriteService.denyRequestGroup(groupMemberId, groupOwnerId); } + + @Override + public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { + groupWriteService.acceptGroupInvite(memberId, groupId, targetId); + } } 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 c4ab95475..31ac4d667 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 @@ -9,4 +9,7 @@ public interface GroupWriteService { void inviteGroup(final Long memberId, final Long groupId, final Long targetId); void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId); + + void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId); + } 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 c1eee618a..8485277b4 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 @@ -94,4 +94,28 @@ public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) } } + 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.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + targetId, groupMember.getId()); + + 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/repository/MemberRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java index 62042ca10..9e8f9b31d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java @@ -26,6 +26,4 @@ int updateMemberNotificationEnabled( @Param("memberId") Long memberId, @Param("notificationEnabled") Boolean notificationEnabled ); - - Optional findMemberByTag(String tag); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java index fda8ce67c..82dd8241b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java @@ -31,13 +31,14 @@ public Binding capsuleSkinFailBinding() { @Bean public Queue groupInviteFailQueue() { - return new Queue(RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getFailComponent(), true); + return new Queue( + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getFailComponent(), true); } @Bean public DirectExchange groupInviteFailExchange() { return new DirectExchange( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getFailComponent()); + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getFailComponent()); } @Bean @@ -48,6 +49,26 @@ public Binding groupInviteFailBinding() { .withQueueName(); } + @Bean + public Queue groupAcceptFailQueue() { + return new Queue( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getFailComponent(), true); + } + + @Bean + public DirectExchange groupAcceptFailExchange() { + return new DirectExchange( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getFailComponent()); + } + + @Bean + public Binding groupAcceptFailBinding() { + return BindingBuilder + .bind(groupAcceptFailQueue()) + .to(groupAcceptFailExchange()) + .withQueueName(); + } + @Bean public Queue friendRequestFailQueue() { return new Queue( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java index cf0b91e0f..4f615029c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java @@ -17,9 +17,15 @@ public enum RabbitmqComponentConstants { "fail.notification.friendAccept.queue"), FRIEND_ACCEPT_NOTIFICATION_EXCHANGE("notification.friendAccept.exchange", "fail.notification.friendAccept.exchange"), - GROUP_INVITE_QUEUE("notification.groupInvite.queue", "fail.notification.groupInvite.queue"), - GROUP_INVITE_EXCHANGE("notification.groupInvite.exchange", - "fail.notification.groupInvite.exchange"); + GROUP_INVITE_NOTIFICATION_QUEUE("notification.groupInvite.queue", + "fail.notification.groupInvite.queue"), + GROUP_INVITE_NOTIFICATION_EXCHANGE("notification.groupInvite.exchange", + "fail.notification.groupInvite.exchange"), + + GROUP_ACCEPT_NOTIFICATION_QUEUE("notification.groupAccept.queue", + "fail.notification.groupAccept.queue"), + GROUP_ACCEPT_NOTIFICATION_EXCHANGE("notification.groupAccept.exchange", + "fail.notification.groupAccept.exchange"); private final String successComponent; private final String failComponent; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java index 3b9763aff..8343c1f3a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java @@ -43,13 +43,14 @@ public Binding capsuleSkinBinding() { @Bean public Queue groupInviteQueue() { - return new Queue(RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getSuccessComponent(), true); + return new Queue( + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getSuccessComponent(), true); } @Bean public DirectExchange groupInviteExchange() { return new DirectExchange( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getSuccessComponent()); + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getSuccessComponent()); } @Bean @@ -60,6 +61,26 @@ public Binding groupInviteBinding() { .withQueueName(); } + @Bean + public Queue groupAcceptQueue() { + return new Queue( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent(), true); + } + + @Bean + public DirectExchange groupAcceptExchange() { + return new DirectExchange( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getSuccessComponent()); + } + + @Bean + public Binding groupAcceptBinding() { + return BindingBuilder + .bind(groupAcceptQueue()) + .to(groupAcceptExchange()) + .withQueueName(); + } + @Bean public Queue friendRequestQueue() { return new Queue( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java new file mode 100644 index 000000000..cb4e24be3 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java @@ -0,0 +1,27 @@ +package site.timecapsulearchive.core.infra.queue.data.dto; + +import lombok.Builder; +import site.timecapsulearchive.core.domain.member.entity.NotificationStatus; + +@Builder +public record GroupAcceptNotificationDto( + Long targetId, + NotificationStatus notificationStatus, + String title, + String text +) { + + public static GroupAcceptNotificationDto createOf( + final Long targetId, + final String groupMemberNickname + ) { + final NotificationRequestMessage groupAcceptRequest = NotificationRequestMessage.GROUP_ACCEPT; + + return new GroupAcceptNotificationDto( + targetId, + groupAcceptRequest.getStatus(), + groupAcceptRequest.getTitle(), + groupAcceptRequest.buildPrefixText(groupMemberNickname) + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java index 5cffeba8e..c0bdc06b4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java @@ -6,7 +6,8 @@ public enum NotificationRequestMessage { CAPSULE_SKIN(NotificationStatus.SUCCESS, "캡슐 스킨 생성 알림", "이 생성되었습니다."), FRIEND_REQUEST(NotificationStatus.SUCCESS, "친구 요청 알림", "님 으로부터 친구 요청이 왔습니다."), FRIEND_ACCEPT(NotificationStatus.SUCCESS, "친구 수락 알림", "님이 친구 요청을 수락하였습니다."), - GROUP_INVITE(NotificationStatus.SUCCESS, "그룹 초대 알림", "님으로부터 그룹 초대 요청이 왔습니다."); + GROUP_INVITE(NotificationStatus.SUCCESS, "그룹 초대 알림", "님으로부터 그룹 초대 요청이 왔습니다."), + GROUP_ACCEPT(NotificationStatus.SUCCESS, "그룹 수락 알림", "님이 그룹 요청을 수락하였습니다."); private final NotificationStatus status; private final String title; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java index b41e4da39..888748a02 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java @@ -8,6 +8,7 @@ import site.timecapsulearchive.core.infra.queue.data.dto.FriendAcceptNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.FriendReqNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.FriendsReqNotificationsDto; +import site.timecapsulearchive.core.infra.queue.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.GroupInviteNotificationDto; @Component @@ -71,9 +72,20 @@ public void sendGroupInviteMessage( final List targetIds ) { basicRabbitTemplate.convertAndSend( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getSuccessComponent(), - RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getSuccessComponent(), + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getSuccessComponent(), + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getSuccessComponent(), GroupInviteNotificationDto.createOf(ownerNickname, groupProfileUrl, targetIds) ); } + + public void sendGroupAcceptMessage( + final String groupMemberNickname, + final Long targetId + ) { + basicRabbitTemplate.convertAndSend( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getSuccessComponent(), + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent(), + GroupAcceptNotificationDto.createOf(targetId, groupMemberNickname) + ); + } } From b8f5d7317c99e5367e3bad61b816f8acd336d96a Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 11 May 2024 22:49:07 +0900 Subject: [PATCH 010/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EA=B0=9C=EB=B4=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capsule/entity/GroupCapsuleOpen.java | 8 +++++ .../exception/GroupCapsuleOpenException.java | 11 +++++++ .../GroupCapsuleOpenNotFoundException.java | 11 +++++++ .../group_capsule/api/GroupCapsuleApi.java | 26 ++++++++++++++++ .../api/GroupCapsuleApiController.java | 13 +++++++- .../GroupCapsuleOpenQueryRepository.java | 12 +++++++ .../GroupCapsuleOpenRepository.java | 4 +++ .../service/GroupCapsuleService.java | 31 +++++++++++++++++++ .../core/global/error/ErrorCode.java | 3 ++ 9 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenNotFoundException.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java index b2c08a19e..d1e94f550 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java @@ -45,4 +45,12 @@ private GroupCapsuleOpen(Boolean isOpened, Capsule capsule, Member member) { this.capsule = Objects.requireNonNull(capsule); this.member = Objects.requireNonNull(member); } + + public static GroupCapsuleOpen createOf(Member member, Capsule capsule, Boolean isOpened) { + return new GroupCapsuleOpen(isOpened, capsule, member); + } + + public void open() { + this.isOpened = Boolean.TRUE; + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java new file mode 100644 index 000000000..3c1f9a59d --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.capsule.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class GroupCapsuleOpenException extends BusinessException { + + public GroupCapsuleOpenException() { + super(ErrorCode.NOT_GROUP_CAPSULE_ERROR); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenNotFoundException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenNotFoundException.java new file mode 100644 index 000000000..fe59e10c2 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenNotFoundException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.capsule.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class GroupCapsuleOpenNotFoundException extends BusinessException { + + public GroupCapsuleOpenNotFoundException() { + super(ErrorCode.GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java index 38a4fa382..d1647f8d7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java @@ -153,6 +153,32 @@ ResponseEntity> getMyGroupCapsules( ZonedDateTime createAt ); + @Operation( + summary = "그룹 캡슐 개봉", + description = """ + 그룹원이 그룹 캡슐을 개봉한다.
캡슐을 만들 때의 그룹원 모두가 캡슐을 개봉해야만 캡슐이 개봉된다. + """, + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group capsule"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리 완료" + ), + @ApiResponse( + responseCode = "404", + description = "그룹 캡슐의 개봉 상태를 찾을 수 없는 경우 예외가 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + }) + ResponseEntity> openCapsule( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "개봉할 그룹 캡슐 아이디", required = true) + @PathVariable("capsule_id") Long capsuleId + ); + @Operation( summary = "그룹 캡슐 24시간 이내 수정", description = "사용자가 생성한 그룹 캡슐의 생성 시간이 24시간 이내라면 수정한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java index c7e7f6af1..9b6089e3a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java @@ -30,7 +30,7 @@ import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; @RestController -@RequestMapping("/groups-capsules") +@RequestMapping("/group-capsules") @RequiredArgsConstructor public class GroupCapsuleApiController implements GroupCapsuleApi { @@ -137,6 +137,17 @@ public ResponseEntity> getMyGroupCapsules( ); } + @PostMapping("/{capsule_id}/open") + @Override + public ResponseEntity> openCapsule( + @AuthenticationPrincipal final Long memberId, + @PathVariable(value = "capsule_id") final Long capsuleId + ) { + groupCapsuleService.openGroupCapsule(memberId, capsuleId); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + } + @Override public ResponseEntity updateGroupCapsuleByIdAndGroupId( Long groupId, Long capsuleId, GroupCapsuleUpdateRequest request) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java index 5a9bacd2b..acf0a17a2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java @@ -1,5 +1,8 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.repository; +import static site.timecapsulearchive.core.domain.capsule.entity.QGroupCapsuleOpen.groupCapsuleOpen; + +import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; @@ -17,6 +20,7 @@ public class GroupCapsuleOpenQueryRepository { private final JdbcTemplate jdbcTemplate; + private final JPAQueryFactory jpaQueryFactory; public void bulkSave(final List groupMemberIds, final Capsule capsule) { jdbcTemplate.batchUpdate( @@ -43,4 +47,12 @@ public int getBatchSize() { } ); } + + public List findIsOpenedByMemberIdAndCapsuleId(final Long capsuleId) { + return jpaQueryFactory + .select(groupCapsuleOpen.isOpened) + .from(groupCapsuleOpen) + .where(groupCapsuleOpen.capsule.id.eq(capsuleId)) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java index a46f6209c..ce02c9eee 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java @@ -1,8 +1,12 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.repository; +import java.util.Optional; import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; public interface GroupCapsuleOpenRepository extends Repository { + void save(GroupCapsuleOpen groupCapsuleOpen); + + Optional findByMemberIdAndCapsuleId(Long memberId, Long capsuleId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java index fa264165a..709c8e2b7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java @@ -2,6 +2,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.locationtech.jts.geom.Point; import org.springframework.data.domain.Slice; @@ -9,12 +10,16 @@ import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; +import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; import site.timecapsulearchive.core.domain.capsule.exception.CapsuleNotFondException; +import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleCreateRequestDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleSummaryDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.MyGroupCapsuleDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.group.entity.Group; @@ -27,6 +32,8 @@ public class GroupCapsuleService { private final CapsuleRepository capsuleRepository; private final GroupCapsuleQueryRepository groupCapsuleQueryRepository; + private final GroupCapsuleOpenRepository groupCapsuleOpenRepository; + private final GroupCapsuleOpenQueryRepository groupCapsuleOpenQueryRepository; @Transactional public Capsule saveGroupCapsule( @@ -88,5 +95,29 @@ public Slice findMyGroupCapsuleSlice( ) { return groupCapsuleQueryRepository.findMyGroupCapsuleSlice(memberId, size, createdAt); } + + /** + * 해당 그룹원이 그룹 캡슐을 개봉한다. 모든 그룹원이 개봉하면 캡슐이 개봉된다. + * + * @param memberId 캡슐을 개봉할 그룹원 + * @param capsuleId 개봉할 캡슐 아이디 + */ + @Transactional + public void openGroupCapsule(final Long memberId, final Long capsuleId) { + GroupCapsuleOpen groupCapsuleOpen = groupCapsuleOpenRepository.findByMemberIdAndCapsuleId( + memberId, + capsuleId) + .orElseThrow(GroupCapsuleOpenNotFoundException::new); + groupCapsuleOpen.open(); + + List capsuleOpens = groupCapsuleOpenQueryRepository.findIsOpenedByMemberIdAndCapsuleId( + capsuleId); + + boolean allGroupMemberOpened = capsuleOpens.stream() + .allMatch(isOpened -> isOpened.equals(Boolean.TRUE)); + if (allGroupMemberOpened) { + capsuleRepository.updateIsOpenedTrue(memberId, capsuleId); + } + } } 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 d1f5dffa2..29a741814 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 @@ -52,6 +52,9 @@ public enum ErrorCode { //capsule CAPSULE_NOT_FOUND_ERROR(404, "CAPSULE-001", "캡슐을 찾지 못하였습니다."), NO_CAPSULE_AUTHORITY_ERROR(403, "CAPSULE-002", "캡슐에 접근 권한이 없습니다."), + NOT_GROUP_CAPSULE_ERROR(400, "CAPSULE-003", + "그룹 캡슐이 아니라 그룹 캡슐 개봉을 할 수 없습니다. 비밀, 친구 캡슐 개봉을 사용하세요"), + GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR(404, "CAPSULE-004", "그룹 캡슐 개봉상태를 찾을 수 없습니다."), //friend FRIEND_NOT_FOUND_ERROR(404, "FRIEND-001", "친구를 찾지 못하였습니다"), From 620df4eced94ff12df87d22fd78560a18167c7bc Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 12 May 2024 12:45:32 +0900 Subject: [PATCH 011/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EA=B0=9C=EB=B4=89=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=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 --- .../domain/GroupCapsuleOpenFixture.java | 4 + .../service/GroupCapsuleServiceTest.java | 109 +++++++++++++++--- .../GroupCapsuleOpenQueryRepositoryTest.java | 4 +- 3 files changed, 101 insertions(+), 16 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java index 7ee3e0029..f555ad68a 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java @@ -18,4 +18,8 @@ public static List groupCapsuleOpens(Boolean isOpened, Capsule ).toList(); } + public static GroupCapsuleOpen groupCapsuleOpen(Member member, Capsule capsule, + Boolean isOpened) { + return GroupCapsuleOpen.createOf(member, capsule, isOpened); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java index 83864e475..f2c95ac23 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java @@ -1,34 +1,55 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.service; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.anyLong; 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 static org.mockito.Mockito.verifyNoInteractions; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import site.timecapsulearchive.core.common.fixture.domain.CapsuleFixture; +import site.timecapsulearchive.core.common.fixture.domain.CapsuleSkinFixture; +import site.timecapsulearchive.core.common.fixture.domain.GroupCapsuleOpenFixture; +import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.CapsuleDtoFixture; +import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; +import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; +import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; import site.timecapsulearchive.core.domain.capsule.generic_capsule.data.dto.CapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; -import site.timecapsulearchive.core.domain.capsule.group_capsule.service.GroupCapsuleService; +import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberSummaryDto; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.global.error.ErrorCode; class GroupCapsuleServiceTest { private final Long capsuleId = 1L; private final int groupMemberCount = 3; + private final CapsuleRepository capsuleRepository = mock(CapsuleRepository.class); private final GroupCapsuleQueryRepository groupCapsuleQueryRepository = mock( GroupCapsuleQueryRepository.class); - private final GroupCapsuleService groupCapsuleService; + private final GroupCapsuleOpenRepository groupCapsuleOpenRepository = mock( + GroupCapsuleOpenRepository.class); + private final GroupCapsuleOpenQueryRepository groupCapsuleOpenQueryRepository = mock( + GroupCapsuleOpenQueryRepository.class); - public GroupCapsuleServiceTest() { - CapsuleRepository capsuleRepository = mock(CapsuleRepository.class); - this.groupCapsuleService = new GroupCapsuleService(capsuleRepository, groupCapsuleQueryRepository); - } + private final GroupCapsuleService groupCapsuleService = new GroupCapsuleService( + capsuleRepository, groupCapsuleQueryRepository, groupCapsuleOpenRepository, + groupCapsuleOpenQueryRepository + ); @Test void 개봉된_그룹_캡슐의_상세_내용을_볼_수_있다() { @@ -42,7 +63,6 @@ public GroupCapsuleServiceTest() { GroupCapsuleDetailDto response = groupCapsuleService.findGroupCapsuleDetailByGroupIdAndCapsuleId( capsuleId); - //then SoftAssertions.assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); @@ -69,7 +89,6 @@ public GroupCapsuleServiceTest() { GroupCapsuleDetailDto response = groupCapsuleService.findGroupCapsuleDetailByGroupIdAndCapsuleId( capsuleId); - //then SoftAssertions.assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); @@ -96,7 +115,6 @@ public GroupCapsuleServiceTest() { GroupCapsuleDetailDto response = groupCapsuleService.findGroupCapsuleDetailByGroupIdAndCapsuleId( capsuleId); - //then SoftAssertions.assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); @@ -116,14 +134,14 @@ public GroupCapsuleServiceTest() { //given given( groupCapsuleQueryRepository.findGroupCapsuleDetailDtoByCapsuleId(anyLong())).willReturn( - CapsuleDtoFixture.getGroupCapsuleDetailDto(capsuleId, false, ZonedDateTime.now().minusDays(5), 3) + CapsuleDtoFixture.getGroupCapsuleDetailDto(capsuleId, false, + ZonedDateTime.now().minusDays(5), 3) ); //when GroupCapsuleDetailDto response = groupCapsuleService.findGroupCapsuleDetailByGroupIdAndCapsuleId( capsuleId); - //then SoftAssertions.assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); @@ -141,14 +159,14 @@ public GroupCapsuleServiceTest() { //given given( groupCapsuleQueryRepository.findGroupCapsuleDetailDtoByCapsuleId(anyLong())).willReturn( - CapsuleDtoFixture.getGroupCapsuleDetailDto(capsuleId, false, ZonedDateTime.now().plusDays(5), 3) + CapsuleDtoFixture.getGroupCapsuleDetailDto(capsuleId, false, + ZonedDateTime.now().plusDays(5), 3) ); //when GroupCapsuleDetailDto response = groupCapsuleService.findGroupCapsuleDetailByGroupIdAndCapsuleId( capsuleId); - //then SoftAssertions.assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); @@ -166,14 +184,14 @@ public GroupCapsuleServiceTest() { //given given( groupCapsuleQueryRepository.findGroupCapsuleDetailDtoByCapsuleId(anyLong())).willReturn( - CapsuleDtoFixture.getGroupCapsuleDetailDto(capsuleId, true, ZonedDateTime.now().plusDays(5), 3) + CapsuleDtoFixture.getGroupCapsuleDetailDto(capsuleId, true, + ZonedDateTime.now().plusDays(5), 3) ); //when GroupCapsuleDetailDto response = groupCapsuleService.findGroupCapsuleDetailByGroupIdAndCapsuleId( capsuleId); - //then SoftAssertions.assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); @@ -186,4 +204,65 @@ public GroupCapsuleServiceTest() { }); } + @Test + void 그룹_캡슐_개봉이_없는_캡슐을_개봉하려는_경우_예외가_발생한다() { + //given + Long memberId = 1L; + Long capsuleId = 1L; + given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> groupCapsuleService.openGroupCapsule(memberId, capsuleId)) + .isInstanceOf(GroupCapsuleOpenNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 모든_그룹원이_캡슐을_개봉하지_않은_경우_캡슐은_개봉되지_않는다() { + //given + Long memberId = 1L; + Long capsuleId = 1L; + given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsuleOpen()); + given(groupCapsuleOpenQueryRepository.findIsOpenedByMemberIdAndCapsuleId(anyLong())) + .willReturn(List.of(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE)); + + //when + groupCapsuleService.openGroupCapsule(memberId, capsuleId); + + //then + verifyNoInteractions(capsuleRepository); + } + + private Optional groupCapsuleOpen() { + Member member = MemberFixture.member(0); + CapsuleSkin capsuleSkin = CapsuleSkinFixture.capsuleSkin(member); + + return Optional.of( + GroupCapsuleOpenFixture.groupCapsuleOpen( + MemberFixture.member(0), + CapsuleFixture.capsule(member, capsuleSkin, CapsuleType.GROUP), + Boolean.FALSE + ) + ); + } + + @Test + void 모든_그룹원이_캡슐을_개봉한_경우_캡슐은_개봉된다() { + //given + Long memberId = 1L; + Long capsuleId = 1L; + given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsuleOpen()); + given(groupCapsuleOpenQueryRepository.findIsOpenedByMemberIdAndCapsuleId(anyLong())) + .willReturn(List.of(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE)); + + //when + groupCapsuleService.openGroupCapsule(memberId, capsuleId); + + //then + verify(capsuleRepository, times(1)).updateIsOpenedTrue(anyLong(), anyLong()); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java index ccb33429c..f9dd6ed0b 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import java.util.List; import javax.sql.DataSource; @@ -36,11 +37,12 @@ class GroupCapsuleOpenQueryRepositoryTest extends RepositoryTest { private List groupCapsuleOpens; public GroupCapsuleOpenQueryRepositoryTest( + JPAQueryFactory jpaQueryFactory, JdbcTemplate jdbcTemplate, DataSource dataSource ) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate); + this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate, jpaQueryFactory); } @Transactional From a289073d86bb3823985ebdfb05f02e93cba7f6ee Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 12 May 2024 23:29:21 +0900 Subject: [PATCH 012/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=AC=B8=EC=84=9C=ED=99=94=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=9D=BC?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/friend/api/FriendApi.java | 3 +- .../friend/api/FriendApiController.java | 54 +++++++++++-------- .../response/FriendReqStatusResponse.java | 13 ----- .../domain/friend/service/FriendService.java | 5 +- .../core/domain/group/api/GroupApi.java | 12 +++++ .../domain/group/api/GroupApiController.java | 6 +-- .../group/service/GroupServiceImpl.java | 4 +- .../service/write/GroupWriteService.java | 2 +- .../service/write/GroupWriteServiceImpl.java | 4 +- 9 files changed, 54 insertions(+), 49 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java index 436c43636..e42ccca02 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java @@ -12,7 +12,6 @@ import org.springframework.http.ResponseEntity; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; -import site.timecapsulearchive.core.domain.friend.data.response.FriendReqStatusResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; @@ -129,7 +128,7 @@ ResponseEntity> denyFriendRequest( description = "외부 API 요청 실패" ) }) - ResponseEntity> requestFriend( + ResponseEntity> requestFriend( Long memberId, @Parameter(in = ParameterIn.PATH, description = "친구 아이디", required = true, schema = @Schema()) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java index 56c2e0755..3eb3fc44c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java @@ -19,7 +19,6 @@ import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; -import site.timecapsulearchive.core.domain.friend.data.response.FriendReqStatusResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; @@ -89,7 +88,9 @@ public ResponseEntity> deleteFriend( friendService.deleteFriend(memberId, friendId); return ResponseEntity.ok( - ApiSpec.empty(SuccessCode.SUCCESS) + ApiSpec.empty( + SuccessCode.SUCCESS + ) ); } @@ -101,23 +102,26 @@ public ResponseEntity> denyFriendRequest( friendService.denyRequestFriend(memberId, friendId); - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); - + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); } @PostMapping(value = "/{friend_id}/request") @Override - public ResponseEntity> requestFriend( + public ResponseEntity> requestFriend( @AuthenticationPrincipal final Long memberId, @PathVariable("friend_id") final Long friendId) { - return ResponseEntity.accepted() - .body( - ApiSpec.success( - SuccessCode.SUCCESS, - friendService.requestFriend(memberId, friendId) - ) - ); + friendService.requestFriend(memberId, friendId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); } @PostMapping(value = "/requests") @@ -128,8 +132,11 @@ public ResponseEntity> requestFriends( ) { friendFacade.requestFriends(memberId, request.friendIds()); - return ResponseEntity.accepted() - .body(ApiSpec.empty(SuccessCode.ACCEPTED)); + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); } @PostMapping(value = "/{friend_id}/accept-request") @@ -140,7 +147,11 @@ public ResponseEntity> acceptFriendRequest( ) { friendService.acceptFriend(memberId, friendId); - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); } @PostMapping( @@ -173,12 +184,11 @@ public ResponseEntity> searchFriendByTag @AuthenticationPrincipal Long memberId, @RequestParam(value = "friend_tag") final String tag ) { - return ResponseEntity.ok() - .body( - ApiSpec.success( - SuccessCode.SUCCESS, - friendService.searchFriend(memberId, tag) - ) - ); + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + friendService.searchFriend(memberId, tag) + ) + ); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java deleted file mode 100644 index 307da6a73..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package site.timecapsulearchive.core.domain.friend.data.response; - -import org.springframework.http.HttpStatus; - -public record FriendReqStatusResponse( - HttpStatus httpStatus, - String result -) { - - public static FriendReqStatusResponse success() { - return new FriendReqStatusResponse(HttpStatus.ACCEPTED, "친구 요청 메시지 전송 성공!"); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 1c57d6eb2..68fb8cf6f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -13,7 +13,6 @@ import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; -import site.timecapsulearchive.core.domain.friend.data.response.FriendReqStatusResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; @@ -43,7 +42,7 @@ public class FriendService { private final SocialNotificationManager socialNotificationManager; private final TransactionTemplate transactionTemplate; - public FriendReqStatusResponse requestFriend(final Long memberId, final Long friendId) { + public void requestFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); validateTwoWayInvite(memberId, friendId); @@ -63,8 +62,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { }); socialNotificationManager.sendFriendReqMessage(owner.getNickname(), friendId); - - return FriendReqStatusResponse.success(); } private void validateFriendDuplicateId(final Long memberId, final Long friendId) { 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 219fc6299..be38b0b37 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 @@ -31,6 +31,14 @@ public interface GroupApi { @ApiResponse( responseCode = "200", description = "처리 완료" + ), + @ApiResponse( + responseCode = "404", + description = "그룹 초대 찾기 실패" + ), + @ApiResponse( + responseCode = "500", + description = "외부 API 요청 실패" ) }) ResponseEntity> acceptGroupInvitation( @@ -53,6 +61,10 @@ ResponseEntity> acceptGroupInvitation( @ApiResponse( responseCode = "200", description = "처리완료" + ), + @ApiResponse( + responseCode = "500", + description = "외부 API 요청 실패" ) }) ResponseEntity> createGroup( 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 b6ffad11d..d0eb7feeb 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 @@ -82,12 +82,12 @@ public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { return null; } - @DeleteMapping(value = "/reject/{group_owner_id}") + @DeleteMapping(value = "/reject/{target_id}") public ResponseEntity> rejectGroupInvitation( @AuthenticationPrincipal final Long memberId, - @PathVariable("group_owner_id") final Long groupOwnerId) { + @PathVariable("target_id") final Long targetId) { - groupService.denyRequestGroup(memberId, groupOwnerId); + groupService.rejectRequestGroup(memberId, targetId); return ResponseEntity.ok( ApiSpec.empty( 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 f993f3e97..efba68fbc 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 @@ -48,8 +48,8 @@ public void inviteGroup(final Long memberId, final Long groupId, final Long targ } @Override - public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { - groupWriteService.denyRequestGroup(groupMemberId, groupOwnerId); + public void rejectRequestGroup(final Long groupMemberId, final Long targetId) { + groupWriteService.rejectRequestGroup(groupMemberId, targetId); } @Override 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 31ac4d667..88db72623 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 @@ -8,7 +8,7 @@ public interface GroupWriteService { void inviteGroup(final Long memberId, final Long groupId, final Long targetId); - void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId); + void rejectRequestGroup(final Long memberId, final Long targetId); void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId); 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 8485277b4..dd80715ec 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 @@ -85,9 +85,9 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Transactional - public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { + public void rejectRequestGroup(final Long memberId, final Long targetId) { final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - groupOwnerId, groupMemberId); + targetId, memberId); if (isDenyRequest != 1) { throw new GroupInviteNotFoundException(); From d68800b075a5e86b252bf039f24d615e82b550d7 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 13 May 2024 21:29:11 +0900 Subject: [PATCH 013/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=B4=88=EB=8C=80=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dependency/TestTransactionTemplate.java | 21 +++ .../common/fixture/dto/GroupDtoFixture.java | 23 +++ .../group/service/GroupWriteServiceTest.java | 154 ++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/TestTransactionTemplate.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupDtoFixture.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/TestTransactionTemplate.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/TestTransactionTemplate.java new file mode 100644 index 000000000..36a7970d4 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/TestTransactionTemplate.java @@ -0,0 +1,21 @@ +package site.timecapsulearchive.core.common.dependency; + +import static org.mockito.Mockito.spy; + +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.support.SimpleTransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +public class TestTransactionTemplate extends TransactionTemplate { + + public static TestTransactionTemplate spied() { + return spy(new TestTransactionTemplate()); + } + + @Override + public T execute(TransactionCallback action) throws TransactionException { + return action.doInTransaction(new SimpleTransactionStatus()); + } + +} 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 new file mode 100644 index 000000000..ca4196785 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupDtoFixture.java @@ -0,0 +1,23 @@ +package site.timecapsulearchive.core.common.fixture.dto; + +import java.util.List; +import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; + +public class GroupDtoFixture { + + public static GroupCreateDto groupCreateDto(List targetIds) { + return GroupCreateDto.builder() + .groupName("testGroupName") + .groupProfileUrl("testGroupProfileUrl") + .groupImage("testGroupImage") + .description("testDescription") + .targetIds(targetIds) + .build(); + } + + public static GroupOwnerSummaryDto groupOwnerSummaryDto(Boolean isOwner) { + return new GroupOwnerSummaryDto("testNickname", isOwner, "testGroupProfileUrl"); + } + +} 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 new file mode 100644 index 000000000..89d5f2cf9 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupWriteServiceTest.java @@ -0,0 +1,154 @@ +package site.timecapsulearchive.core.domain.group.service; + + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyList; +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.List; +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.MemberFixture; +import site.timecapsulearchive.core.common.fixture.dto.GroupDtoFixture; +import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; +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.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; + +class GroupWriteServiceTest { + + 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 GroupWriteService groupWriteService = new GroupWriteServiceImpl( + memberRepository, + groupRepository, + memberGroupRepository, + groupInviteRepository, + transactionTemplate, + socialNotificationManager + ); + + @Test + void 그룹장이_그룹원들을_포함하여_그룹을_생성하면_그룹원들에게_그룹초대_알림이_요청된다() { + //given + Long memberId = 1L; + List targetIds = List.of(2L, 3L, 4L, 5L); + GroupCreateDto dto = GroupDtoFixture.groupCreateDto(targetIds); + given(memberRepository.findMemberById(memberId)).willReturn( + Optional.ofNullable(MemberFixture.member(1))); + + //when + groupWriteService.createGroup(memberId, dto); + + //then + verify(socialNotificationManager, times(1)).sendGroupInviteMessage( + anyString(), anyString(), anyList()); + } + + @Test + void 그룹장이_그룹초대를_할_때_존재하지_않는_그룹장_아이디면_예외가_발생한다() { + //given + Long memberId = -1L; + List targetIds = List.of(2L, 3L, 4L, 5L); + GroupCreateDto dto = GroupDtoFixture.groupCreateDto(targetIds); + given(memberRepository.findMemberById(memberId)).willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> groupWriteService.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(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(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()); + } + + +} + From da3cb0e84b31ff7031805b970a4ada81916dda7a Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 10 May 2024 01:27:42 +0900 Subject: [PATCH 014/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EA=B1=B0=EB=B6=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/friend/service/FriendService.java | 4 ++-- .../core/domain/group/api/GroupApi.java | 10 ++++------ .../core/domain/group/api/GroupApiController.java | 12 +++++++++--- .../exception/GroupInviteNotFoundException.java | 11 +++++++++++ .../GroupInviteRepository.java | 2 ++ .../core/domain/group/service/GroupServiceImpl.java | 5 +++++ .../group/service/read/GroupReadServiceImpl.java | 4 +--- .../group/service/write/GroupWriteService.java | 2 ++ .../group/service/write/GroupWriteServiceImpl.java | 13 ++++++++++++- .../core/global/error/ErrorCode.java | 1 + 10 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupInviteNotFoundException.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 913700af2..82450f8e1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -114,7 +114,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Transactional - public void denyRequestFriend(Long memberId, Long friendId) { + public void denyRequestFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); final FriendInvite friendInvite = friendInviteRepository .findFriendInviteByOwnerIdAndFriendId(memberId, friendId).orElseThrow( @@ -127,7 +127,7 @@ public void denyRequestFriend(Long memberId, Long friendId) { public void deleteFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); - List memberFriends = memberFriendRepository + final List memberFriends = memberFriendRepository .findMemberFriendByOwnerIdAndFriendId(memberId, friendId); memberFriends.forEach(memberFriendRepository::delete); 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 e80e56508..f78fab9d9 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 @@ -111,13 +111,11 @@ ResponseEntity deleteGroupMember( description = "처리 완료" ) }) - @PostMapping(value = "/groups/{group_id}/members/{member_id}/deny-invitation") - ResponseEntity denyGroupInvitation( - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, + ResponseEntity> rejectGroupInvitation( + Long memberId, - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true, schema = @Schema()) - @PathVariable("member_id") Long memberId + @Parameter(in = ParameterIn.PATH, description = "그룹 초대 대상 아이디", required = true) + Long groupOwnerId ); @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 10665e7a8..c61f31e46 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 @@ -6,6 +6,7 @@ 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; @@ -70,9 +71,14 @@ public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { return null; } - @Override - public ResponseEntity denyGroupInvitation(Long groupId, Long memberId) { - return null; + @DeleteMapping(value = "/reject/{group_owner_id}") + public ResponseEntity> rejectGroupInvitation( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_owner_id") final Long groupOwnerId) { + + groupService.denyRequestGroup(memberId, groupOwnerId); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } @GetMapping( 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/exception/GroupInviteNotFoundException.java new file mode 100644 index 000000000..39ad8bb61 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/exception/GroupInviteNotFoundException.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 GroupInviteNotFoundException extends BusinessException { + + public GroupInviteNotFoundException() { + super(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR); + } +} 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 87d2cce84..e18663d1a 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 @@ -7,5 +7,7 @@ public interface GroupInviteRepository extends Repository, GroupInviteQueryRepository { void save(GroupInvite groupInvite); + + int deleteGroupInviteByGroupOwnerIdAndGroupMemberId(Long groupOwnerId, Long groupMemberId); } 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 9d6342b83..359f2ddec 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 @@ -46,4 +46,9 @@ public void createGroup(final Long memberId, final GroupCreateDto dto) { public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { groupWriteService.inviteGroup(memberId, groupId, targetId); } + + @Override + public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { + groupWriteService.denyRequestGroup(groupMemberId, groupOwnerId); + } } 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/read/GroupReadServiceImpl.java index 2d833a0ca..d3066d287 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/read/GroupReadServiceImpl.java @@ -13,17 +13,16 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class GroupReadServiceImpl implements GroupReadService { private final GroupRepository groupRepository; - @Transactional(readOnly = true) public Group findGroupById(final Long groupId) { return groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); } - @Transactional(readOnly = true) public Slice findGroupsSlice( final Long memberId, final int size, @@ -32,7 +31,6 @@ public Slice findGroupsSlice( return groupRepository.findGroupsSlice(memberId, size, createdAt); } - @Transactional(readOnly = true) public GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupId(groupId) .orElseThrow(GroupNotFoundException::new); 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 891dc13d9..c4ab95475 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 @@ -7,4 +7,6 @@ public interface GroupWriteService { void createGroup(final Long memberId, final GroupCreateDto dto); void inviteGroup(final Long memberId, final Long groupId, final Long targetId); + + void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId); } 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 f2da391e7..c1eee618a 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 @@ -4,6 +4,7 @@ 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.data.dto.GroupCreateDto; @@ -11,6 +12,7 @@ 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.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; @@ -53,7 +55,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { dto.groupProfileUrl(), dto.targetIds()); } - @Override public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { final Member groupOwner = memberRepository.findMemberById(memberId).orElseThrow( MemberNotFoundException::new); @@ -83,4 +84,14 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { summaryDto[0].groupProfileUrl(), List.of(targetId)); } + @Transactional + public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { + final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + groupOwnerId, groupMemberId); + + if (isDenyRequest != 1) { + throw new GroupInviteNotFoundException(); + } + } + } 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 d7a3667b1..434fbf85e 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 @@ -61,6 +61,7 @@ public enum ErrorCode { GROUP_CREATE_ERROR(400, "GROUP-001", "그룹 생성에 실패하였습니다."), GROUP_NOT_FOUND_ERROR(404, "GROUP-002", "그룹을 찾을 수 없습니다"), GROUP_OWNER_AUTHENTICATE_ERROR(400, "GROUP-003", "그룹장이 아닙니다."), + GROUP_INVITATION_NOT_FOUND_ERROR(404, "GROUP-004", "그룹 초대를 찾을 수 없습니다"), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), From dde37043666782cb8b3ee0ea74a4bc2c0094ea1c Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 10 May 2024 01:35:18 +0900 Subject: [PATCH 015/195] =?UTF-8?q?refact=20:=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EA=B1=B0=EC=A0=88=20=EC=BF=BC=EB=A6=AC=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 - 기존 delete는 삭제 시 조회 쿼리 한번 더 발생함 --- .../repository/FriendInviteRepository.java | 2 +- .../domain/friend/service/FriendService.java | 20 ++++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java index c7c94a40a..df8643a77 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java @@ -24,7 +24,7 @@ List findFriendInviteWithMembersByOwnerIdAndFriendId( Optional findFriendInviteByOwnerIdAndFriendId(Long memberId, Long targetId); - void deleteFriendInviteById(Long id); + int deleteFriendInviteByOwnerIdAndFriendId(Long memberId, Long targetId); void delete(FriendInvite friendInvite); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 82450f8e1..b070484a4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -44,7 +44,6 @@ public class FriendService { private final TransactionTemplate transactionTemplate; public FriendReqStatusResponse requestFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); validateTwoWayInvite(memberId, friendId); final Member owner = memberRepository.findMemberById(memberId).orElseThrow( @@ -67,12 +66,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { return FriendReqStatusResponse.success(); } - private void validateFriendDuplicateId(final Long memberId, final Long friendId) { - if (memberId.equals(friendId)) { - throw new FriendDuplicateIdException(); - } - } - private void validateTwoWayInvite(final Long memberId, final Long friendId) { final Optional friendInvite = friendInviteRepository.findFriendInviteByOwnerIdAndFriendId( friendId, memberId); @@ -84,8 +77,6 @@ private void validateTwoWayInvite(final Long memberId, final Long friendId) { public void acceptFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); - final String[] ownerNickname = new String[1]; transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -115,18 +106,15 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void denyRequestFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); - final FriendInvite friendInvite = friendInviteRepository - .findFriendInviteByOwnerIdAndFriendId(memberId, friendId).orElseThrow( - FriendNotFoundException::new); + int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, memberId); - friendInviteRepository.deleteFriendInviteById(friendInvite.getId()); + if (isDenyRequest != 1) { + throw new FriendTwoWayInviteException(); + } } @Transactional public void deleteFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); - final List memberFriends = memberFriendRepository .findMemberFriendByOwnerIdAndFriendId(memberId, friendId); From 8d86e12380a8a5294634affd9b3090564a491778 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 10 May 2024 01:37:38 +0900 Subject: [PATCH 016/195] =?UTF-8?q?refact=20:=20=EB=B9=84=EB=B0=80=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EC=98=A4=ED=94=88=20=EC=BF=BC=EB=A6=AC=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 - 쿼리 실행 실패 시 예외 처리 추가 --- .../generic_capsule/repository/CapsuleRepository.java | 2 +- .../capsule/generic_capsule/service/CapsuleService.java | 6 +++++- .../core/domain/friend/service/FriendService.java | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java index 4f5378f46..03908bba4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java @@ -19,7 +19,7 @@ Optional findCapsuleByMemberIdAndCapsuleId( @Modifying(clearAutomatically = true) @Query("UPDATE Capsule c SET c.isOpened = true WHERE c.id = :capsuleId and c.member.id = :memberId") - void updateIsOpenedTrue( + int updateIsOpenedTrue( @Param("memberId") Long memberId, @Param("capsuleId") Long capsuleId ); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java index f1414845b..150af31c1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java @@ -74,7 +74,11 @@ public Capsule findCapsuleByMemberIdAndCapsuleId(final Long memberId, final Long @Transactional public void updateIsOpenedTrue(final Long memberId, final Long capsuleId) { - capsuleRepository.updateIsOpenedTrue(memberId, capsuleId); + int isOpenedTrue = capsuleRepository.updateIsOpenedTrue(memberId, capsuleId); + + if (isOpenedTrue != 1) { + throw new CapsuleNotFondException(); + } } @Transactional diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index b070484a4..618e2d645 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -17,7 +17,6 @@ import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; -import site.timecapsulearchive.core.domain.friend.exception.FriendDuplicateIdException; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendTwoWayInviteException; @@ -106,7 +105,8 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void denyRequestFriend(final Long memberId, final Long friendId) { - int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, memberId); + int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, + memberId); if (isDenyRequest != 1) { throw new FriendTwoWayInviteException(); From babcf859b8166ecbadbff02691c7425c7287a026 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 12 May 2024 01:35:18 +0900 Subject: [PATCH 017/195] =?UTF-8?q?feat=20:=20memberId,=20FriendId=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 필요 없는줄 알았다가 다시 추가 --- .../core/domain/friend/service/FriendService.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 618e2d645..1c57d6eb2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -17,6 +17,7 @@ import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; +import site.timecapsulearchive.core.domain.friend.exception.FriendDuplicateIdException; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendTwoWayInviteException; @@ -43,6 +44,7 @@ public class FriendService { private final TransactionTemplate transactionTemplate; public FriendReqStatusResponse requestFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); validateTwoWayInvite(memberId, friendId); final Member owner = memberRepository.findMemberById(memberId).orElseThrow( @@ -65,6 +67,12 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { return FriendReqStatusResponse.success(); } + private void validateFriendDuplicateId(final Long memberId, final Long friendId) { + if (memberId.equals(friendId)) { + throw new FriendDuplicateIdException(); + } + } + private void validateTwoWayInvite(final Long memberId, final Long friendId) { final Optional friendInvite = friendInviteRepository.findFriendInviteByOwnerIdAndFriendId( friendId, memberId); @@ -76,6 +84,8 @@ private void validateTwoWayInvite(final Long memberId, final Long friendId) { public void acceptFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); + final String[] ownerNickname = new String[1]; transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -105,6 +115,8 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void denyRequestFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); + int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, memberId); @@ -115,6 +127,8 @@ public void denyRequestFriend(final Long memberId, final Long friendId) { @Transactional public void deleteFriend(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); + final List memberFriends = memberFriendRepository .findMemberFriendByOwnerIdAndFriendId(memberId, friendId); From 9971076268afa67d0473c43b61e4988b3acfe003 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 13 May 2024 21:53:03 +0900 Subject: [PATCH 018/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EA=B1=B0=EB=B6=80=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/service/GroupWriteServiceTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) 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 89d5f2cf9..6e9143cbc 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 @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.group.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.anyString; @@ -18,6 +19,7 @@ import site.timecapsulearchive.core.common.fixture.dto.GroupDtoFixture; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupOwnerSummaryDto; +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; @@ -149,6 +151,37 @@ class GroupWriteServiceTest { .hasMessageContaining(ErrorCode.GROUP_OWNER_AUTHENTICATE_ERROR.getMessage()); } + @Test + void 그룹원은_그룹초대를_1을_반환하면_거부할_수_있다() { + //given + Long memberId = 1L; + Long targetId = 2L; + + given(groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + targetId, memberId)).willReturn(1); + + //when + // then + assertThatCode(() -> groupWriteService.denyRequestGroup(memberId, targetId)) + .doesNotThrowAnyException(); + } + + @Test + void 그룹원은_그룹초대를_0을_반환하면_거부가_실패_한다() { + //given + Long memberId = 1L; + Long targetId = 2L; + + given(groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + targetId, memberId)).willReturn(0); + + //when + // then + assertThatThrownBy(() -> groupWriteService.denyRequestGroup(memberId, targetId)) + .isInstanceOf(GroupInviteNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); + } + } From 2bd870c20b00cd17093bfba4b598f30122d3f703 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 12 May 2024 03:04:11 +0900 Subject: [PATCH 019/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=88=98=EB=9D=BD=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/api/GroupApi.java | 14 +++++----- .../domain/group/api/GroupApiController.java | 23 +++++++++++++--- .../core/domain/group/entity/MemberGroup.java | 4 +++ .../group/service/GroupServiceImpl.java | 5 ++++ .../service/write/GroupWriteService.java | 3 +++ .../service/write/GroupWriteServiceImpl.java | 24 +++++++++++++++++ .../member/repository/MemberRepository.java | 2 -- .../rabbitmq/RabbitFailedComponentConfig.java | 25 +++++++++++++++-- .../rabbitmq/RabbitmqComponentConstants.java | 12 ++++++--- .../config/rabbitmq/RabbitmqConfig.java | 25 +++++++++++++++-- .../data/dto/GroupAcceptNotificationDto.java | 27 +++++++++++++++++++ .../data/dto/NotificationRequestMessage.java | 3 ++- .../manager/SocialNotificationManager.java | 16 +++++++++-- 13 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java 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 f78fab9d9..219fc6299 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 @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; 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; @@ -34,13 +33,14 @@ public interface GroupApi { description = "처리 완료" ) }) - @PostMapping(value = "/groups/{group_id}/members/{member_id}/accept-invitation") - ResponseEntity acceptGroupInvitation( - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, + ResponseEntity> acceptGroupInvitation( + Long memberId, - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true, schema = @Schema()) - @PathVariable("member_id") Long memberId + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) + Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) + Long targetId ); @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 c61f31e46..b6ffad11d 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 @@ -37,9 +37,20 @@ public class GroupApiController implements GroupApi { private final S3UrlGenerator s3UrlGenerator; private final S3PreSignedUrlManager s3PreSignedUrlManager; + @PostMapping(value = "/accept/{group_id}/member/{target_id}") @Override - public ResponseEntity acceptGroupInvitation(Long groupId, Long memberId) { - return null; + 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 @@ -56,7 +67,7 @@ public ResponseEntity> createGroup( return ResponseEntity.ok( ApiSpec.empty( - SuccessCode.SUCCESS + SuccessCode.ACCEPTED ) ); } @@ -78,7 +89,11 @@ public ResponseEntity> rejectGroupInvitation( groupService.denyRequestGroup(memberId, groupOwnerId); - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); } @GetMapping( 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/entity/MemberGroup.java index d9c4f5192..67c37404c 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/entity/MemberGroup.java @@ -46,4 +46,8 @@ private MemberGroup(Boolean isOwner, Member member, Group group) { public static MemberGroup createGroupOwner(Member member, Group group) { return new MemberGroup(true, member, group); } + + public static MemberGroup createGroupMember(Member member, Group group) { + return new MemberGroup(false, member, group); + } } 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 359f2ddec..f993f3e97 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 @@ -51,4 +51,9 @@ public void inviteGroup(final Long memberId, final Long groupId, final Long targ public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { groupWriteService.denyRequestGroup(groupMemberId, groupOwnerId); } + + @Override + public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { + groupWriteService.acceptGroupInvite(memberId, groupId, targetId); + } } 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 c4ab95475..31ac4d667 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 @@ -9,4 +9,7 @@ public interface GroupWriteService { void inviteGroup(final Long memberId, final Long groupId, final Long targetId); void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId); + + void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId); + } 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 c1eee618a..8485277b4 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 @@ -94,4 +94,28 @@ public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) } } + 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.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + targetId, groupMember.getId()); + + 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/repository/MemberRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java index 62042ca10..9e8f9b31d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java @@ -26,6 +26,4 @@ int updateMemberNotificationEnabled( @Param("memberId") Long memberId, @Param("notificationEnabled") Boolean notificationEnabled ); - - Optional findMemberByTag(String tag); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java index fda8ce67c..82dd8241b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java @@ -31,13 +31,14 @@ public Binding capsuleSkinFailBinding() { @Bean public Queue groupInviteFailQueue() { - return new Queue(RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getFailComponent(), true); + return new Queue( + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getFailComponent(), true); } @Bean public DirectExchange groupInviteFailExchange() { return new DirectExchange( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getFailComponent()); + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getFailComponent()); } @Bean @@ -48,6 +49,26 @@ public Binding groupInviteFailBinding() { .withQueueName(); } + @Bean + public Queue groupAcceptFailQueue() { + return new Queue( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getFailComponent(), true); + } + + @Bean + public DirectExchange groupAcceptFailExchange() { + return new DirectExchange( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getFailComponent()); + } + + @Bean + public Binding groupAcceptFailBinding() { + return BindingBuilder + .bind(groupAcceptFailQueue()) + .to(groupAcceptFailExchange()) + .withQueueName(); + } + @Bean public Queue friendRequestFailQueue() { return new Queue( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java index cf0b91e0f..4f615029c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java @@ -17,9 +17,15 @@ public enum RabbitmqComponentConstants { "fail.notification.friendAccept.queue"), FRIEND_ACCEPT_NOTIFICATION_EXCHANGE("notification.friendAccept.exchange", "fail.notification.friendAccept.exchange"), - GROUP_INVITE_QUEUE("notification.groupInvite.queue", "fail.notification.groupInvite.queue"), - GROUP_INVITE_EXCHANGE("notification.groupInvite.exchange", - "fail.notification.groupInvite.exchange"); + GROUP_INVITE_NOTIFICATION_QUEUE("notification.groupInvite.queue", + "fail.notification.groupInvite.queue"), + GROUP_INVITE_NOTIFICATION_EXCHANGE("notification.groupInvite.exchange", + "fail.notification.groupInvite.exchange"), + + GROUP_ACCEPT_NOTIFICATION_QUEUE("notification.groupAccept.queue", + "fail.notification.groupAccept.queue"), + GROUP_ACCEPT_NOTIFICATION_EXCHANGE("notification.groupAccept.exchange", + "fail.notification.groupAccept.exchange"); private final String successComponent; private final String failComponent; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java index 3b9763aff..8343c1f3a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java @@ -43,13 +43,14 @@ public Binding capsuleSkinBinding() { @Bean public Queue groupInviteQueue() { - return new Queue(RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getSuccessComponent(), true); + return new Queue( + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getSuccessComponent(), true); } @Bean public DirectExchange groupInviteExchange() { return new DirectExchange( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getSuccessComponent()); + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getSuccessComponent()); } @Bean @@ -60,6 +61,26 @@ public Binding groupInviteBinding() { .withQueueName(); } + @Bean + public Queue groupAcceptQueue() { + return new Queue( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent(), true); + } + + @Bean + public DirectExchange groupAcceptExchange() { + return new DirectExchange( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getSuccessComponent()); + } + + @Bean + public Binding groupAcceptBinding() { + return BindingBuilder + .bind(groupAcceptQueue()) + .to(groupAcceptExchange()) + .withQueueName(); + } + @Bean public Queue friendRequestQueue() { return new Queue( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java new file mode 100644 index 000000000..cb4e24be3 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/GroupAcceptNotificationDto.java @@ -0,0 +1,27 @@ +package site.timecapsulearchive.core.infra.queue.data.dto; + +import lombok.Builder; +import site.timecapsulearchive.core.domain.member.entity.NotificationStatus; + +@Builder +public record GroupAcceptNotificationDto( + Long targetId, + NotificationStatus notificationStatus, + String title, + String text +) { + + public static GroupAcceptNotificationDto createOf( + final Long targetId, + final String groupMemberNickname + ) { + final NotificationRequestMessage groupAcceptRequest = NotificationRequestMessage.GROUP_ACCEPT; + + return new GroupAcceptNotificationDto( + targetId, + groupAcceptRequest.getStatus(), + groupAcceptRequest.getTitle(), + groupAcceptRequest.buildPrefixText(groupMemberNickname) + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java index 5cffeba8e..c0bdc06b4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/data/dto/NotificationRequestMessage.java @@ -6,7 +6,8 @@ public enum NotificationRequestMessage { CAPSULE_SKIN(NotificationStatus.SUCCESS, "캡슐 스킨 생성 알림", "이 생성되었습니다."), FRIEND_REQUEST(NotificationStatus.SUCCESS, "친구 요청 알림", "님 으로부터 친구 요청이 왔습니다."), FRIEND_ACCEPT(NotificationStatus.SUCCESS, "친구 수락 알림", "님이 친구 요청을 수락하였습니다."), - GROUP_INVITE(NotificationStatus.SUCCESS, "그룹 초대 알림", "님으로부터 그룹 초대 요청이 왔습니다."); + GROUP_INVITE(NotificationStatus.SUCCESS, "그룹 초대 알림", "님으로부터 그룹 초대 요청이 왔습니다."), + GROUP_ACCEPT(NotificationStatus.SUCCESS, "그룹 수락 알림", "님이 그룹 요청을 수락하였습니다."); private final NotificationStatus status; private final String title; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java index b41e4da39..888748a02 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java @@ -8,6 +8,7 @@ import site.timecapsulearchive.core.infra.queue.data.dto.FriendAcceptNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.FriendReqNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.FriendsReqNotificationsDto; +import site.timecapsulearchive.core.infra.queue.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.GroupInviteNotificationDto; @Component @@ -71,9 +72,20 @@ public void sendGroupInviteMessage( final List targetIds ) { basicRabbitTemplate.convertAndSend( - RabbitmqComponentConstants.GROUP_INVITE_EXCHANGE.getSuccessComponent(), - RabbitmqComponentConstants.GROUP_INVITE_QUEUE.getSuccessComponent(), + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getSuccessComponent(), + RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getSuccessComponent(), GroupInviteNotificationDto.createOf(ownerNickname, groupProfileUrl, targetIds) ); } + + public void sendGroupAcceptMessage( + final String groupMemberNickname, + final Long targetId + ) { + basicRabbitTemplate.convertAndSend( + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_EXCHANGE.getSuccessComponent(), + RabbitmqComponentConstants.GROUP_ACCEPT_NOTIFICATION_QUEUE.getSuccessComponent(), + GroupAcceptNotificationDto.createOf(targetId, groupMemberNickname) + ); + } } From 38be18892115f11ea9a366f9c78c68feddd23f73 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 12 May 2024 23:29:21 +0900 Subject: [PATCH 020/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=AC=B8=EC=84=9C=ED=99=94=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=9D=BC?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/friend/api/FriendApi.java | 3 +- .../friend/api/FriendApiController.java | 54 +++++++++++-------- .../response/FriendReqStatusResponse.java | 13 ----- .../domain/friend/service/FriendService.java | 5 +- .../core/domain/group/api/GroupApi.java | 12 +++++ .../domain/group/api/GroupApiController.java | 6 +-- .../group/service/GroupServiceImpl.java | 4 +- .../service/write/GroupWriteService.java | 2 +- .../service/write/GroupWriteServiceImpl.java | 4 +- 9 files changed, 54 insertions(+), 49 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java index 436c43636..e42ccca02 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java @@ -12,7 +12,6 @@ import org.springframework.http.ResponseEntity; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; -import site.timecapsulearchive.core.domain.friend.data.response.FriendReqStatusResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; @@ -129,7 +128,7 @@ ResponseEntity> denyFriendRequest( description = "외부 API 요청 실패" ) }) - ResponseEntity> requestFriend( + ResponseEntity> requestFriend( Long memberId, @Parameter(in = ParameterIn.PATH, description = "친구 아이디", required = true, schema = @Schema()) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java index 56c2e0755..3eb3fc44c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java @@ -19,7 +19,6 @@ import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; -import site.timecapsulearchive.core.domain.friend.data.response.FriendReqStatusResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; @@ -89,7 +88,9 @@ public ResponseEntity> deleteFriend( friendService.deleteFriend(memberId, friendId); return ResponseEntity.ok( - ApiSpec.empty(SuccessCode.SUCCESS) + ApiSpec.empty( + SuccessCode.SUCCESS + ) ); } @@ -101,23 +102,26 @@ public ResponseEntity> denyFriendRequest( friendService.denyRequestFriend(memberId, friendId); - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); - + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); } @PostMapping(value = "/{friend_id}/request") @Override - public ResponseEntity> requestFriend( + public ResponseEntity> requestFriend( @AuthenticationPrincipal final Long memberId, @PathVariable("friend_id") final Long friendId) { - return ResponseEntity.accepted() - .body( - ApiSpec.success( - SuccessCode.SUCCESS, - friendService.requestFriend(memberId, friendId) - ) - ); + friendService.requestFriend(memberId, friendId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); } @PostMapping(value = "/requests") @@ -128,8 +132,11 @@ public ResponseEntity> requestFriends( ) { friendFacade.requestFriends(memberId, request.friendIds()); - return ResponseEntity.accepted() - .body(ApiSpec.empty(SuccessCode.ACCEPTED)); + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); } @PostMapping(value = "/{friend_id}/accept-request") @@ -140,7 +147,11 @@ public ResponseEntity> acceptFriendRequest( ) { friendService.acceptFriend(memberId, friendId); - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); } @PostMapping( @@ -173,12 +184,11 @@ public ResponseEntity> searchFriendByTag @AuthenticationPrincipal Long memberId, @RequestParam(value = "friend_tag") final String tag ) { - return ResponseEntity.ok() - .body( - ApiSpec.success( - SuccessCode.SUCCESS, - friendService.searchFriend(memberId, tag) - ) - ); + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + friendService.searchFriend(memberId, tag) + ) + ); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java deleted file mode 100644 index 307da6a73..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendReqStatusResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package site.timecapsulearchive.core.domain.friend.data.response; - -import org.springframework.http.HttpStatus; - -public record FriendReqStatusResponse( - HttpStatus httpStatus, - String result -) { - - public static FriendReqStatusResponse success() { - return new FriendReqStatusResponse(HttpStatus.ACCEPTED, "친구 요청 메시지 전송 성공!"); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java index 1c57d6eb2..68fb8cf6f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java @@ -13,7 +13,6 @@ import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; -import site.timecapsulearchive.core.domain.friend.data.response.FriendReqStatusResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; @@ -43,7 +42,7 @@ public class FriendService { private final SocialNotificationManager socialNotificationManager; private final TransactionTemplate transactionTemplate; - public FriendReqStatusResponse requestFriend(final Long memberId, final Long friendId) { + public void requestFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); validateTwoWayInvite(memberId, friendId); @@ -63,8 +62,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { }); socialNotificationManager.sendFriendReqMessage(owner.getNickname(), friendId); - - return FriendReqStatusResponse.success(); } private void validateFriendDuplicateId(final Long memberId, final Long friendId) { 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 219fc6299..be38b0b37 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 @@ -31,6 +31,14 @@ public interface GroupApi { @ApiResponse( responseCode = "200", description = "처리 완료" + ), + @ApiResponse( + responseCode = "404", + description = "그룹 초대 찾기 실패" + ), + @ApiResponse( + responseCode = "500", + description = "외부 API 요청 실패" ) }) ResponseEntity> acceptGroupInvitation( @@ -53,6 +61,10 @@ ResponseEntity> acceptGroupInvitation( @ApiResponse( responseCode = "200", description = "처리완료" + ), + @ApiResponse( + responseCode = "500", + description = "외부 API 요청 실패" ) }) ResponseEntity> createGroup( 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 b6ffad11d..d0eb7feeb 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 @@ -82,12 +82,12 @@ public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { return null; } - @DeleteMapping(value = "/reject/{group_owner_id}") + @DeleteMapping(value = "/reject/{target_id}") public ResponseEntity> rejectGroupInvitation( @AuthenticationPrincipal final Long memberId, - @PathVariable("group_owner_id") final Long groupOwnerId) { + @PathVariable("target_id") final Long targetId) { - groupService.denyRequestGroup(memberId, groupOwnerId); + groupService.rejectRequestGroup(memberId, targetId); return ResponseEntity.ok( ApiSpec.empty( 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 f993f3e97..efba68fbc 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 @@ -48,8 +48,8 @@ public void inviteGroup(final Long memberId, final Long groupId, final Long targ } @Override - public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { - groupWriteService.denyRequestGroup(groupMemberId, groupOwnerId); + public void rejectRequestGroup(final Long groupMemberId, final Long targetId) { + groupWriteService.rejectRequestGroup(groupMemberId, targetId); } @Override 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 31ac4d667..88db72623 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 @@ -8,7 +8,7 @@ public interface GroupWriteService { void inviteGroup(final Long memberId, final Long groupId, final Long targetId); - void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId); + void rejectRequestGroup(final Long memberId, final Long targetId); void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId); 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 8485277b4..dd80715ec 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 @@ -85,9 +85,9 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Transactional - public void denyRequestGroup(final Long groupMemberId, final Long groupOwnerId) { + public void rejectRequestGroup(final Long memberId, final Long targetId) { final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - groupOwnerId, groupMemberId); + targetId, memberId); if (isDenyRequest != 1) { throw new GroupInviteNotFoundException(); From 02a1ff01dd4c5e438ea157c188969c99c7adb228 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 13 May 2024 22:19:59 +0900 Subject: [PATCH 021/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/write/GroupWriteServiceImpl.java | 2 +- .../group/service/GroupWriteServiceTest.java | 54 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 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 dd80715ec..e2198c721 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 @@ -104,7 +104,7 @@ public void acceptGroupInvite(final Long memberId, final Long groupId, final Lon @Override protected void doInTransactionWithoutResult(TransactionStatus status) { final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - targetId, groupMember.getId()); + targetId, memberId); if (isDenyRequest != 1) { throw new GroupInviteNotFoundException(); 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 6e9143cbc..8af8a3acb 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 @@ -4,6 +4,7 @@ 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; @@ -15,6 +16,7 @@ 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.data.dto.GroupCreateDto; @@ -59,7 +61,7 @@ class GroupWriteServiceTest { List targetIds = List.of(2L, 3L, 4L, 5L); GroupCreateDto dto = GroupDtoFixture.groupCreateDto(targetIds); given(memberRepository.findMemberById(memberId)).willReturn( - Optional.ofNullable(MemberFixture.member(1))); + Optional.of(MemberFixture.member(1))); //when groupWriteService.createGroup(memberId, dto); @@ -152,7 +154,7 @@ class GroupWriteServiceTest { } @Test - void 그룹원은_그룹초대를_1을_반환하면_거부할_수_있다() { + void 그룹원은_그룹초대_삭제에서_1을_반환하면_거부할_수_있다() { //given Long memberId = 1L; Long targetId = 2L; @@ -162,12 +164,12 @@ class GroupWriteServiceTest { //when // then - assertThatCode(() -> groupWriteService.denyRequestGroup(memberId, targetId)) + assertThatCode(() -> groupWriteService.rejectRequestGroup(memberId, targetId)) .doesNotThrowAnyException(); } @Test - void 그룹원은_그룹초대를_0을_반환하면_거부가_실패_한다() { + void 그룹원은_그룹초대_삭제에서_0을_반환하면_거부가_실패_한다() { //given Long memberId = 1L; Long targetId = 2L; @@ -177,7 +179,49 @@ class GroupWriteServiceTest { //when // then - assertThatThrownBy(() -> groupWriteService.denyRequestGroup(memberId, targetId)) + assertThatThrownBy(() -> groupWriteService.rejectRequestGroup(memberId, 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.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + 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.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( + targetId, memberId)).willReturn(0); + + //when + //then + assertThatThrownBy(() -> groupWriteService.acceptGroupInvite(memberId, groupId, targetId)) .isInstanceOf(GroupInviteNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); } From c3f7609e47f645ff6b39bdd8e4b5c4f8c9ad83e0 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 13 May 2024 22:55:21 +0900 Subject: [PATCH 022/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=EC=97=90=20=EA=B7=B8=EB=A3=B9=20id=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존의 그룹 owner와 그룹 member로는 식별 안됨 --- .../core/domain/group/api/GroupApi.java | 2 ++ .../domain/group/api/GroupApiController.java | 5 +++-- .../core/domain/group/entity/GroupInvite.java | 10 ++++++--- .../GroupInviteRepository.java | 3 ++- .../group/service/GroupServiceImpl.java | 5 +++-- .../service/write/GroupWriteService.java | 2 +- .../service/write/GroupWriteServiceImpl.java | 12 +++++----- .../db/migration/V26__group_invite_add.sql | 3 +++ .../group/service/GroupWriteServiceTest.java | 22 ++++++++++--------- 9 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 backend/core/src/main/resources/db/migration/V26__group_invite_add.sql 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 be38b0b37..7f79e682a 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 @@ -126,6 +126,8 @@ ResponseEntity deleteGroupMember( 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/api/GroupApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/api/GroupApiController.java index d0eb7feeb..3efb49539 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 @@ -82,12 +82,13 @@ public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { return null; } - @DeleteMapping(value = "/reject/{target_id}") + @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, targetId); + groupService.rejectRequestGroup(memberId, groupId, targetId); return ResponseEntity.ok( ApiSpec.empty( 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 434e245ba..690014c01 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,6 +26,9 @@ public class GroupInvite extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "group_id", nullable = false) + private Long groupId; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "group_owner_id", nullable = false) private Member groupOwner; @@ -34,13 +37,14 @@ public class GroupInvite extends BaseEntity { @JoinColumn(name = "group_member_id", nullable = false) private Member groupMember; - private GroupInvite(Member groupOwner, Member groupMember) { + private GroupInvite(Long groupId, Member groupOwner, Member groupMember) { + this.groupId = groupId; this.groupOwner = groupOwner; this.groupMember = groupMember; } - public static GroupInvite createOf(Member groupOwner, Member groupMember) { - return new GroupInvite(groupOwner, groupMember); + public static GroupInvite createOf(Long groupId, Member groupOwner, Member groupMember) { + return new GroupInvite(groupId, groupOwner, groupMember); } } 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 e18663d1a..1331805dd 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 @@ -8,6 +8,7 @@ public interface GroupInviteRepository extends Repository, void save(GroupInvite groupInvite); - int deleteGroupInviteByGroupOwnerIdAndGroupMemberId(Long groupOwnerId, Long groupMemberId); + int deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId(Long groupId, Long groupOwnerId, + Long groupMemberId); } 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 efba68fbc..b1252d0ea 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 @@ -48,8 +48,9 @@ public void inviteGroup(final Long memberId, final Long groupId, final Long targ } @Override - public void rejectRequestGroup(final Long groupMemberId, final Long targetId) { - groupWriteService.rejectRequestGroup(groupMemberId, targetId); + public void rejectRequestGroup(final Long groupMemberId, final Long groupId, + final Long targetId) { + groupWriteService.rejectRequestGroup(groupMemberId, groupId, targetId); } @Override 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 88db72623..6eb08e6f6 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 @@ -8,7 +8,7 @@ public interface GroupWriteService { void inviteGroup(final Long memberId, final Long groupId, final Long targetId); - void rejectRequestGroup(final Long memberId, 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); 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 e2198c721..b2065bee5 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 @@ -62,7 +62,7 @@ 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(groupOwner, groupMember); + final GroupInvite groupInvite = GroupInvite.createOf(groupId, groupOwner, groupMember); final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; @@ -85,9 +85,9 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } @Transactional - public void rejectRequestGroup(final Long memberId, final Long targetId) { - final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - targetId, memberId); + 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(); @@ -103,8 +103,8 @@ public void acceptGroupInvite(final Long memberId, final Long groupId, final Lon transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { - final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - targetId, memberId); + final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId); if (isDenyRequest != 1) { throw new GroupInviteNotFoundException(); diff --git a/backend/core/src/main/resources/db/migration/V26__group_invite_add.sql b/backend/core/src/main/resources/db/migration/V26__group_invite_add.sql new file mode 100644 index 000000000..61254b875 --- /dev/null +++ b/backend/core/src/main/resources/db/migration/V26__group_invite_add.sql @@ -0,0 +1,3 @@ +alter table group_invite add column group_id BIGINT; +ALTER TABLE group_invite + ADD CONSTRAINT fk_group_invite_group_id FOREIGN KEY (group_id) REFERENCES `group` (group_id); \ No newline at end of file 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 8af8a3acb..e3b158f37 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 @@ -157,14 +157,15 @@ class GroupWriteServiceTest { void 그룹원은_그룹초대_삭제에서_1을_반환하면_거부할_수_있다() { //given Long memberId = 1L; + Long groupId = 1L; Long targetId = 2L; - given(groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - targetId, memberId)).willReturn(1); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(1); //when // then - assertThatCode(() -> groupWriteService.rejectRequestGroup(memberId, targetId)) + assertThatCode(() -> groupWriteService.rejectRequestGroup(memberId, groupId, targetId)) .doesNotThrowAnyException(); } @@ -172,14 +173,15 @@ class GroupWriteServiceTest { void 그룹원은_그룹초대_삭제에서_0을_반환하면_거부가_실패_한다() { //given Long memberId = 1L; + Long groupId = 1L; Long targetId = 2L; - given(groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - targetId, memberId)).willReturn(0); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(0); //when // then - assertThatThrownBy(() -> groupWriteService.rejectRequestGroup(memberId, targetId)) + assertThatThrownBy(() -> groupWriteService.rejectRequestGroup(memberId, groupId, targetId)) .isInstanceOf(GroupInviteNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); } @@ -195,8 +197,8 @@ class GroupWriteServiceTest { given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); given(groupRepository.findGroupById(groupId)).willReturn( Optional.of(GroupFixture.group())); - given(groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - targetId, memberId)).willReturn(1); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(1); //when groupWriteService.acceptGroupInvite(memberId, groupId, targetId); @@ -216,8 +218,8 @@ class GroupWriteServiceTest { given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); given(groupRepository.findGroupById(groupId)).willReturn( Optional.of(GroupFixture.group())); - given(groupInviteRepository.deleteGroupInviteByGroupOwnerIdAndGroupMemberId( - targetId, memberId)).willReturn(0); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId)).willReturn(0); //when //then From 6f210f5acf4e51ff8124a3bd8efdf991422f1ba2 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 00:06:28 +0900 Subject: [PATCH 023/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=82=AD=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 024/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../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 025/195] =?UTF-8?q?docs=20:=20=EB=AC=B8=EC=84=9C=ED=99=94?= =?UTF-8?q?=20=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 026/195] =?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 027/195] =?UTF-8?q?fix=20:=20lazy=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?@Modifying=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 028/195] =?UTF-8?q?fix=20:=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../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 029/195] =?UTF-8?q?fix=20:=20read/write,=20group/group=5Fm?= =?UTF-8?q?ember=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 030/195] =?UTF-8?q?fix=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../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()); + } + +} From 0b35efd74840bcbc5770697aeb2f279049ec9ebe Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:29:46 +0900 Subject: [PATCH 031/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=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 --- .../service/command/GroupCommandService.java | 3 +- .../api/command/GroupMemberCommandApi.java | 62 ++++++++++--------- .../GroupMemberCommandApiController.java | 13 ++-- ...java => GroupMemberNotFoundException.java} | 8 +-- .../exception/GroupQuitException.java | 11 ++++ .../exception/NoGroupAuthorityException.java | 11 ++++ .../MemberGroupRepository.java | 3 + .../service/GroupMemberCommandService.java | 32 ++++++++-- .../core/global/error/ErrorCode.java | 11 ++-- 9 files changed, 103 insertions(+), 51 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/{GroupOwnerAuthenticateException.java => GroupMemberNotFoundException.java} (54%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupQuitException.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/NoGroupAuthorityException.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index d1afbc627..508f94fcd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -13,6 +13,7 @@ import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; 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; @@ -94,7 +95,7 @@ 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); + throw new NoGroupAuthorityException(); } } 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 index e32783d4b..76e0271bd 100644 --- 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 @@ -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; @@ -11,48 +12,49 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import site.timecapsulearchive.core.global.common.response.ApiSpec; +import site.timecapsulearchive.core.global.error.ErrorResponse; 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 = "사용자가 속한 그룹을 탈퇴한다.", + description = """ + 그룹 탈퇴를 요청한 사용자가 해당 그룹의 그룹장이 아닌 경우 그룹을 탈퇴한다.
+ 주의 +
    +
  • 그룹 탈퇴를 요청한 사용자가 해당 그룹의 그룹장인 경우 그룹을 탈퇴할 수 없다.
  • +
+ """, security = {@SecurityRequirement(name = "user_token")}, - tags = {"group member"} + 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}/members/quit") - ResponseEntity quitGroup( - @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId - ); + ResponseEntity> quitGroup( + 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_member/api/command/GroupMemberCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApiController.java index be6d3795b..dec282a84 100644 --- 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 @@ -19,14 +19,15 @@ public class GroupMemberCommandApiController implements GroupMemberCommandApi { private final GroupMemberCommandService groupMemberCommandService; + @DeleteMapping(value = "/{group_id}/members/quit") @Override - public ResponseEntity deleteGroupMember(Long groupId, Long memberId) { - return null; - } + public ResponseEntity> quitGroup( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId + ) { + groupMemberCommandService.quitGroup(memberId, groupId); - @Override - public ResponseEntity quitGroup(Long groupId) { - return null; + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } @PostMapping(value = "/invite/{group_id}/member/{target_id}") diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupOwnerAuthenticateException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberNotFoundException.java similarity index 54% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupOwnerAuthenticateException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberNotFoundException.java index ae2cc2b2f..364c387ca 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupOwnerAuthenticateException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberNotFoundException.java @@ -3,9 +3,9 @@ import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.BusinessException; -public class GroupOwnerAuthenticateException extends BusinessException { +public class GroupMemberNotFoundException extends BusinessException { - public GroupOwnerAuthenticateException() { - super(ErrorCode.GROUP_OWNER_AUTHENTICATE_ERROR); + public GroupMemberNotFoundException() { + super(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR); } -} +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupQuitException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupQuitException.java new file mode 100644 index 000000000..88c00a51c --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupQuitException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.group_member.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class GroupQuitException extends BusinessException { + + public GroupQuitException(ErrorCode errorCode) { + super(errorCode); + } +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/NoGroupAuthorityException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/NoGroupAuthorityException.java new file mode 100644 index 000000000..fcbd129ed --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/NoGroupAuthorityException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.group_member.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class NoGroupAuthorityException extends BusinessException { + + public NoGroupAuthorityException() { + super(ErrorCode.NO_GROUP_AUTHORITY_ERROR); + } +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java index cfe9af47e..7155578dd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository; import java.util.List; +import java.util.Optional; import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; @@ -12,4 +13,6 @@ public interface MemberGroupRepository extends Repository, List findMemberGroupsByGroupId(Long groupId); void delete(MemberGroup memberGroup); + + Optional findMemberGroupByMemberIdAndGroupId(Long memberId, Long groupId); } 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 index 5e85ba734..6cdbd065e 100644 --- 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 @@ -7,19 +7,22 @@ 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.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.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.exception.GroupMemberNotFoundException; +import site.timecapsulearchive.core.domain.group_member.exception.GroupQuitException; +import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; 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.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Service @@ -54,7 +57,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { groupId, memberId).orElseThrow(GroupNotFoundException::new); if (!summaryDto[0].isOwner()) { - throw new GroupOwnerAuthenticateException(); + throw new NoGroupAuthorityException(); } groupInviteRepository.save(groupInvite); @@ -98,4 +101,23 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), targetId); } + /** + * 사용자는 사용자가 속한 그룹을 탈퇴한다. + *
주의 - 그룹 탈퇴 시 아래 조건에 해당하면 예외가 발생한다. + *
1. 그룹원이 그룹장인 경우 + * + * @param memberId 그룹에서 탈퇴할 사용자 + * @param groupId 탈퇴할 그룹 아이디 + */ + @Transactional + public void quitGroup(final Long memberId, final Long groupId) { + final MemberGroup groupMember = memberGroupRepository.findMemberGroupByMemberIdAndGroupId( + memberId, groupId) + .orElseThrow(GroupMemberNotFoundException::new); + if (groupMember.getIsOwner()) { + throw new GroupQuitException(ErrorCode.GROUP_OWNER_QUIT_ERROR); + } + + memberGroupRepository.delete(groupMember); + } } 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 2ea8d6fe7..f038f7ccb 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 @@ -60,11 +60,12 @@ public enum ErrorCode { //group GROUP_CREATE_ERROR(400, "GROUP-001", "그룹 생성에 실패하였습니다."), 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", "그룹 캡슐이 존재해 그룹을 삭제할 수 없습니다."), + GROUP_INVITATION_NOT_FOUND_ERROR(404, "GROUP-003", "그룹 초대를 찾을 수 없습니다"), + GROUP_MEMBER_EXIST_ERROR(400, "GROUP-004", "다른 그룹 멤버가 존재해 그룹을 삭제할 수 없습니다."), + NO_GROUP_AUTHORITY_ERROR(403, "GROUP-005", "그룹에 대한 권한이 존재하지 않습니다."), + GROUP_CAPSULE_EXIST_ERROR(400, "GROUP-006", "그룹 캡슐이 존재해 그룹을 삭제할 수 없습니다."), + GROUP_OWNER_QUIT_ERROR(400, "GROUP-007", "그룹장은 그룹을 탈퇴할 수 없습니다."), + GROUP_MEMBER_NOT_FOUND_ERROR(404, "GROUP-008", "그룹에서 해당 멤버를 찾을 수 없습니다."), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), From a559d645b7137adb6ade328de8198f3eb0bad208 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:29:54 +0900 Subject: [PATCH 032/195] =?UTF-8?q?feat=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/GroupCommandServiceTest.java | 3 +- .../GroupMemberCommandServiceTest.java | 75 ++++++++++++++++--- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java index f8246fc22..f7410e847 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java @@ -30,6 +30,7 @@ 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.exception.NoGroupAuthorityException; 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; @@ -122,7 +123,7 @@ class GroupCommandServiceTest { //when //then assertThatThrownBy(() -> groupCommandService.deleteGroup(groupMemberId, groupId)) - .isExactlyInstanceOf(GroupDeleteFailException.class) + .isExactlyInstanceOf(NoGroupAuthorityException.class) .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); } 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 index ae8e1c6c1..9fee1a26c 100644 --- 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 @@ -2,6 +2,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; @@ -16,12 +17,15 @@ 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.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.entity.MemberGroup; 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.exception.GroupQuitException; +import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; 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; @@ -39,7 +43,7 @@ public class GroupMemberCommandServiceTest { SocialNotificationManager.class); private final TransactionTemplate transactionTemplate = TestTransactionTemplate.spied(); - private final GroupMemberCommandService groupQueryService = new GroupMemberCommandService( + private final GroupMemberCommandService groupMemberCommandService = new GroupMemberCommandService( memberRepository, groupRepository, memberGroupRepository, @@ -66,7 +70,7 @@ public class GroupMemberCommandServiceTest { Optional.of(groupOwnerSummaryDto)); //when - groupQueryService.inviteGroup(memberId, groupId, targetId); + groupMemberCommandService.inviteGroup(memberId, groupId, targetId); //then verify(socialNotificationManager, times(1)).sendGroupInviteMessage(anyString(), anyString(), @@ -89,7 +93,7 @@ public class GroupMemberCommandServiceTest { //when //then - assertThatThrownBy(() -> groupQueryService.inviteGroup(memberId, groupId, targetId)) + assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, groupId, targetId)) .isInstanceOf(GroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); } @@ -112,9 +116,9 @@ public class GroupMemberCommandServiceTest { //when //then - assertThatThrownBy(() -> groupQueryService.inviteGroup(memberId, groupId, targetId)) - .isInstanceOf(GroupOwnerAuthenticateException.class) - .hasMessageContaining(ErrorCode.GROUP_OWNER_AUTHENTICATE_ERROR.getMessage()); + assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, groupId, targetId)) + .isInstanceOf(NoGroupAuthorityException.class) + .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); } @Test @@ -129,7 +133,8 @@ public class GroupMemberCommandServiceTest { //when // then - assertThatCode(() -> groupQueryService.rejectRequestGroup(memberId, groupId, targetId)) + assertThatCode( + () -> groupMemberCommandService.rejectRequestGroup(memberId, groupId, targetId)) .doesNotThrowAnyException(); } @@ -146,7 +151,7 @@ public class GroupMemberCommandServiceTest { //when // then assertThatThrownBy( - () -> groupQueryService.rejectRequestGroup(memberId, groupId, targetId)) + () -> groupMemberCommandService.rejectRequestGroup(memberId, groupId, targetId)) .isInstanceOf(GroupInviteNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); } @@ -166,7 +171,7 @@ public class GroupMemberCommandServiceTest { groupId, targetId, memberId)).willReturn(1); //when - groupQueryService.acceptGroupInvite(memberId, groupId, targetId); + groupMemberCommandService.acceptGroupInvite(memberId, groupId, targetId); //then verify(socialNotificationManager, times(1)).sendGroupAcceptMessage(anyString(), anyLong()); @@ -188,9 +193,57 @@ public class GroupMemberCommandServiceTest { //when //then - assertThatThrownBy(() -> groupQueryService.acceptGroupInvite(memberId, groupId, targetId)) + assertThatThrownBy( + () -> groupMemberCommandService.acceptGroupInvite(memberId, groupId, targetId)) .isInstanceOf(GroupInviteNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); } + + @Test + void 그룹장인_사용자가_그룹_탈퇴를_시도하면_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + given(memberGroupRepository.findMemberGroupByMemberIdAndGroupId(groupOwnerId, + groupId)).willReturn(ownerGroupMemberOnly()); + + //when + //then + assertThatThrownBy(() -> groupMemberCommandService.quitGroup(groupOwnerId, groupId)) + .isInstanceOf(GroupQuitException.class) + .hasMessageContaining(ErrorCode.GROUP_OWNER_QUIT_ERROR.getMessage()); + } + + private Optional ownerGroupMemberOnly() { + return Optional.of( + MemberGroupFixture.groupOwner(MemberFixture.memberWithMemberId(1L), + GroupFixture.group()) + ); + } + + @Test + void 그룹장이_아닌_사용자가_그룹_탈퇴를_시도하면_그룹을_탈퇴한다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + given(memberGroupRepository.findMemberGroupByMemberIdAndGroupId(groupOwnerId, + groupId)).willReturn(notOwnerGroupMemberOnly()); + + //when + groupMemberCommandService.quitGroup(groupOwnerId, groupId); + + //then + verify(memberGroupRepository, times(1)).delete(any(MemberGroup.class)); + } + + private Optional notOwnerGroupMemberOnly() { + return Optional.of( + MemberGroupFixture.memberGroup( + MemberFixture.memberWithMemberId(2L), + GroupFixture.group(), + false + ) + ); + } } From 6f59f7d759ba033aad724d6dff6a258383084897 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:38:39 +0900 Subject: [PATCH 033/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=EC=9B=90?= =?UTF-8?q?=20=EC=B6=94=EB=B0=A9=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 --- .../api/command/GroupMemberCommandApi.java | 37 +++++++++++++++++ .../GroupMemberCommandApiController.java | 12 ++++++ .../GroupMemberDuplicatedIdException.java | 11 +++++ .../MemberGroupQueryRepository.java | 1 + .../MemberGroupQueryRepositoryImpl.java | 14 +++++++ .../service/GroupMemberCommandService.java | 41 +++++++++++++++++++ .../core/global/error/ErrorCode.java | 1 + 7 files changed, 117 insertions(+) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberDuplicatedIdException.java 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 index 76e0271bd..22643b401 100644 --- 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 @@ -127,4 +127,41 @@ ResponseEntity> rejectGroupInvitation( @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 = "처리 완료" + ), + @ApiResponse( + responseCode = "400", + description = "그룹장과 삭제하려는 대상 그룹원 아이디가 같은 경우에 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "403", + description = "그룹장이 아니여서 그룹 수정에 대한 권한이 없는 경우 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "삭제하려는 그룹원이 존재하지 않는 경우 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + }) + ResponseEntity> kickGroupMember( + Long groupOwnerId, + + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) + Long groupId, + + @Parameter(in = ParameterIn.PATH, description = "추방할 그룹원 멤버 아이디", required = true) + Long groupMemberId + ); } 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 index dec282a84..0140576f3 100644 --- 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 @@ -76,4 +76,16 @@ public ResponseEntity> rejectGroupInvitation( ) ); } + + @DeleteMapping(value = "/{group_id}/members/{group_member_id}") + @Override + public ResponseEntity> kickGroupMember( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId, + @PathVariable("group_member_id") final Long groupMemberId + ) { + groupMemberCommandService.kickGroupMember(memberId, groupId, groupMemberId); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberDuplicatedIdException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberDuplicatedIdException.java new file mode 100644 index 000000000..6fb29d827 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberDuplicatedIdException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.group_member.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class GroupMemberDuplicatedIdException extends BusinessException { + + public GroupMemberDuplicatedIdException() { + super(ErrorCode.GROUP_MEMBER_DUPLICATED_ID_ERROR); + } +} \ No newline at end of file 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 index 01e11dff6..993d9b185 100644 --- 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 @@ -7,4 +7,5 @@ public interface MemberGroupQueryRepository { Optional findOwnerInMemberGroup(Long groupId, Long memberId); + Optional findIsOwnerByMemberIdAndGroupId(Long groupOwnerId, Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index afdb563ff..a94974b61 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -37,4 +37,18 @@ public Optional findOwnerInMemberGroup(final Long groupId, .fetchFirst() ); } + + @Override + public Optional findIsOwnerByMemberIdAndGroupId( + final Long memberId, + final Long groupId + ) { + return Optional.ofNullable( + jpaQueryFactory + .select(memberGroup.isOwner) + .from(memberGroup) + .where(memberGroup.member.id.eq(memberId).and(memberGroup.group.id.eq(groupId))) + .fetchOne() + ); + } } 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 index 6cdbd065e..91c91079b 100644 --- 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 @@ -14,6 +14,7 @@ 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_member.exception.GroupMemberDuplicatedIdException; import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberNotFoundException; import site.timecapsulearchive.core.domain.group_member.exception.GroupQuitException; import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; @@ -120,4 +121,44 @@ public void quitGroup(final Long memberId, final Long groupId) { memberGroupRepository.delete(groupMember); } + + /** + * 그룹의 회원을 그룹에서 삭제한다. + *
주의 - 그룹원 삭제 시 아래 조건에 해당하면 예외가 발생한다. + *
1. 자신을 삭제하려한 경우 + *
2. 삭제를 요청한 사용자가 그룹장이 아닌 경우 + *
3. 삭제할 대상이 없는 경우 + * + * @param groupOwnerId 그룹장 여부 조회할 대상 사용자 + * @param groupId 해당 그룹 아이디 + * @param groupMemberId 삭제할 그룹원 아이디 + */ + @Transactional + public void kickGroupMember( + final Long groupOwnerId, + final Long groupId, + final Long groupMemberId + ) { + if (groupOwnerId.equals(groupMemberId)) { + throw new GroupMemberDuplicatedIdException(); + } + + checkGroupOwnerShip(groupOwnerId, groupId); + + final MemberGroup memberGroup = memberGroupRepository.findMemberGroupByMemberIdAndGroupId( + groupMemberId, groupId) + .orElseThrow(GroupMemberNotFoundException::new); + + memberGroupRepository.delete(memberGroup); + } + + private void checkGroupOwnerShip(Long groupOwnerId, Long groupId) { + final Boolean isOwner = memberGroupRepository.findIsOwnerByMemberIdAndGroupId( + groupOwnerId, + groupId) + .orElseThrow(GroupMemberNotFoundException::new); + if (!isOwner) { + throw new NoGroupAuthorityException(); + } + } } 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 f038f7ccb..21b9da6e9 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 @@ -66,6 +66,7 @@ public enum ErrorCode { GROUP_CAPSULE_EXIST_ERROR(400, "GROUP-006", "그룹 캡슐이 존재해 그룹을 삭제할 수 없습니다."), GROUP_OWNER_QUIT_ERROR(400, "GROUP-007", "그룹장은 그룹을 탈퇴할 수 없습니다."), GROUP_MEMBER_NOT_FOUND_ERROR(404, "GROUP-008", "그룹에서 해당 멤버를 찾을 수 없습니다."), + GROUP_MEMBER_DUPLICATED_ID_ERROR(400, "GROUP-009", "자신을 추방할 수 없습니다."), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), From 6e75bc9b36cc2e538d386bfcec98f65bb4371a58 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:38:47 +0900 Subject: [PATCH 034/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupMemberCommandServiceTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) 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 index 9fee1a26c..0b41474cf 100644 --- 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 @@ -24,6 +24,8 @@ import site.timecapsulearchive.core.domain.group_member.data.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; import site.timecapsulearchive.core.domain.group_member.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberDuplicatedIdException; +import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberNotFoundException; import site.timecapsulearchive.core.domain.group_member.exception.GroupQuitException; import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository.GroupInviteRepository; @@ -246,4 +248,89 @@ private Optional notOwnerGroupMemberOnly() { ) ); } + + @Test + void 나_자신을_그룹에서_삭제하려하면_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + + //when + //then + assertThatThrownBy( + () -> groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, groupOwnerId)) + .isInstanceOf(GroupMemberDuplicatedIdException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_DUPLICATED_ID_ERROR.getMessage()); + } + + @Test + void 그룹_삭제를_요청한_사용자를_그룹에서_찾을_수_없으면_예외가_발생한다() { + //given + Long notExistGroupOwnerId = 1L; + Long groupId = 1L; + Long groupMemberId = 2L; + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy( + () -> groupMemberCommandService.kickGroupMember(notExistGroupOwnerId, groupId, groupMemberId)) + .isInstanceOf(GroupMemberNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 그룹_삭제를_요청한_사용자가_그룹에서_그룹장이_아니면_예외가_발생한다() { + //given + Long notGroupOwnerId = 1L; + Long groupId = 1L; + Long groupMemberId = 2L; + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.of(Boolean.FALSE)); + + //when + //then + assertThatThrownBy( + () -> groupMemberCommandService.kickGroupMember(notGroupOwnerId, groupId, groupMemberId)) + .isInstanceOf(NoGroupAuthorityException.class) + .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); + } + + @Test + void 그룹_삭제의_대상_그룹원이_그룹에_존재하지_않으면_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + Long notExistGroupMemberId = 2L; + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.of(Boolean.TRUE)); + given(memberGroupRepository.findMemberGroupByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy( + () -> groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, notExistGroupMemberId)) + .isInstanceOf(GroupMemberNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 그룹장이_그룹원을_삭제하면_그룹에서_삭제된다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + Long groupMemberId = 2L; + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.of(Boolean.TRUE)); + given(memberGroupRepository.findMemberGroupByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(notOwnerGroupMemberOnly()); + + //when + groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, groupMemberId); + + //then + verify(memberGroupRepository, times(1)).delete(any(MemberGroup.class)); + } } From d2170a6e277a39f95ffa3e8e1c6d8722b09aa1b3 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:50:28 +0900 Subject: [PATCH 035/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/mapper/CapsuleSkinMapper.java | 5 +-- .../group/api/command/GroupCommandApi.java | 36 +++++++++------- .../command/GroupCommandApiController.java | 12 +++++- .../domain/group/data/dto/GroupUpdateDto.java | 13 ++++++ .../data/reqeust/GroupUpdateRequest.java | 30 ++++++++++--- .../core/domain/group/entity/Group.java | 16 +++++-- .../service/command/GroupCommandService.java | 43 +++++++++++++++++++ .../api/command/GroupMemberCommandApi.java | 4 +- .../service/GroupMemberCommandService.java | 4 +- .../s3/manager/S3PreSignedUrlManager.java | 6 +-- .../core/infra/s3/manager/S3UrlGenerator.java | 2 +- 11 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupUpdateDto.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/data/mapper/CapsuleSkinMapper.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/data/mapper/CapsuleSkinMapper.java index 680988313..9abddbd29 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/data/mapper/CapsuleSkinMapper.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/data/mapper/CapsuleSkinMapper.java @@ -18,7 +18,6 @@ @RequiredArgsConstructor public class CapsuleSkinMapper { - private final S3UrlGenerator s3UrlGenerator; private final S3PreSignedUrlManager s3PreSignedUrlManager; public CapsuleSkinsSliceResponse capsuleSkinSliceToResponse( @@ -57,7 +56,7 @@ public CapsuleSkin createDtoToEntity(CapsuleSkinCreateDto dto, Member member) { return CapsuleSkin.builder() .skinName(dto.skinName()) .imageUrl( - s3UrlGenerator.generateFileName(member.getId(), dto.directory(), dto.imageUrl())) + S3UrlGenerator.generateFileName(member.getId(), dto.directory(), dto.imageUrl())) .member(member) .build(); } @@ -73,7 +72,7 @@ public CapsuleSkinMessageDto createDtoToMessageDto( .skinName(dto.skinName()) .imageUrl( s3PreSignedUrlManager.getS3PreSignedUrlForGet( - s3UrlGenerator.generateFileName(memberId, dto.directory(), dto.imageUrl()) + S3UrlGenerator.generateFileName(memberId, dto.directory(), dto.imageUrl()) )) .motionName(dto.motionName()) .retarget(dto.retarget()) 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 index bee129442..46b4e690f 100644 --- 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 @@ -84,26 +84,32 @@ ResponseEntity> createGroup( GroupCreateRequest request ); - @Operation( - summary = "그룹 수정", - description = "그룹장인 경우에 그룹의 기본 정보들을 수정한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) @ApiResponses(value = { @ApiResponse( - responseCode = "202", - description = "처리 시작" + responseCode = "200", + description = "처리 완료" + ),@ApiResponse( + responseCode = "400", + description = "잘못된 타입이나 값을 입력하는 경우 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "403", + description = "그룹장이 아니여서 그룹 수정에 대한 권한이 없는 경우 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "권한을 확인할 수 있는 그룹원이 없는 경우, 수정하려는 그룹이 없는 경우 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) }) - @PatchMapping( - value = "/groups/{group_id}", - consumes = {"multipart/form-data"} - ) - ResponseEntity updateGroupById( + ResponseEntity> updateGroupById( + Long memberId, + @Parameter(in = ParameterIn.PATH, description = "수정할 그룹 아이디", required = true, schema = @Schema()) - @PathVariable("group_id") Long groupId, + Long groupId, - @ModelAttribute GroupUpdateRequest request + 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 index 3dc4ade88..7e318cccf 100644 --- 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 @@ -5,6 +5,7 @@ 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.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -56,8 +57,15 @@ public ResponseEntity> deleteGroupById( return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } + @PatchMapping(value = "/{group_id}", consumes = {"application/json"}) @Override - public ResponseEntity updateGroupById(Long groupId, GroupUpdateRequest request) { - return null; + public ResponseEntity> updateGroupById( + @AuthenticationPrincipal Long memberId, + @PathVariable("group_id") Long groupId, + @RequestBody GroupUpdateRequest request + ) { + groupCommandService.updateGroup(memberId, groupId, request.toDto()); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupUpdateDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupUpdateDto.java new file mode 100644 index 000000000..f22bd75fa --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupUpdateDto.java @@ -0,0 +1,13 @@ +package site.timecapsulearchive.core.domain.group.data.dto; + +import lombok.Builder; + +@Builder +public record GroupUpdateDto( + String groupName, + String groupDescription, + String groupImageProfileFileName, + String groupImageDirectory +) { + +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupUpdateRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupUpdateRequest.java index d37472708..9fd3a2fe6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupUpdateRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupUpdateRequest.java @@ -1,16 +1,36 @@ package site.timecapsulearchive.core.domain.group.data.reqeust; import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.web.multipart.MultipartFile; +import jakarta.validation.constraints.NotBlank; +import site.timecapsulearchive.core.domain.group.data.dto.GroupUpdateDto; +import site.timecapsulearchive.core.global.common.valid.annotation.Image; @Schema(description = "그룹 업데이트 포맷") public record GroupUpdateRequest( - String name, + @Schema(description = "그룹 이름") + @NotBlank(message = "그룹 이름은 필수 입니다.") + String groupName, - String description, + @Schema(description = "그룹 설명") + @NotBlank(message = "그룹 설명은 필수 입니다.") + String groupDescription, - MultipartFile profileImage + @Schema(description = "그룹 프로필 이미지 파일 이름") + @Image + String groupImageProfileFileName, + + @Schema(description = "그룹 이미지 디렉토리") + @NotBlank(message = "그룹 이미지 디렉토리는 필수 입니다.") + String groupImageDirectory ) { -} + public GroupUpdateDto toDto() { + return GroupUpdateDto.builder() + .groupName(groupName) + .groupDescription(groupDescription) + .groupImageProfileFileName(groupImageProfileFileName) + .groupImageDirectory(groupImageDirectory) + .build(); + } +} \ No newline at end of file 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 9f8e0eacd..bccadc20a 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 @@ -1,20 +1,16 @@ package site.timecapsulearchive.core.domain.group.entity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import java.util.List; import java.util.Objects; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import site.timecapsulearchive.core.domain.capsule.entity.Capsule; import site.timecapsulearchive.core.domain.group.exception.GroupCreateException; import site.timecapsulearchive.core.global.entity.BaseEntity; @@ -51,4 +47,16 @@ private Group(String groupName, String groupDescription, String groupProfileUrl) this.groupDescription = groupDescription; this.groupProfileUrl = groupProfileUrl; } + + public void updateGroupName(String groupName) { + this.groupName = groupName; + } + + public void updateGroupDescription(String groupDescription) { + this.groupDescription = groupDescription; + } + + public void updateGroupProfileUrl(String groupProfileUrl) { + this.groupProfileUrl = groupProfileUrl; + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index 508f94fcd..f12106bbd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.group.service.command; +import jakarta.transaction.Transactional; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -8,11 +9,13 @@ 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.GroupUpdateDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberNotFoundException; import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository.MemberGroupRepository; @@ -22,6 +25,7 @@ import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; import site.timecapsulearchive.core.infra.s3.manager.S3ObjectManager; +import site.timecapsulearchive.core.infra.s3.manager.S3UrlGenerator; @Service @RequiredArgsConstructor @@ -114,4 +118,43 @@ private void checkGroupCapsuleExist(Long groupId) { throw new GroupDeleteFailException(ErrorCode.GROUP_CAPSULE_EXIST_ERROR); } } + + /** + * 그룹 정보를 업데이트한다. + *
주의 - 그룹 정보 변경 시 아래 조건에 해당하면 예외가 발생한다. + *
1. 해당 그룹원을 찾을 수 없는 경우 + *
2. 그룹장이 아닌 경우 + *
3. 그룹을 찾을 수 없는 경우 + * + * @param groupOwnerId 그룹장 아이디 + * @param groupId 그룹 아이디 + * @param dto 업데이트할 그룹 정보 + */ + @Transactional + public void updateGroup(Long groupOwnerId, Long groupId, GroupUpdateDto dto) { + checkGroupOwnership(groupOwnerId, groupId); + + Group group = groupRepository.findGroupById(groupId) + .orElseThrow(GroupNotFoundException::new); + + group.updateGroupName(dto.groupName()); + group.updateGroupDescription(dto.groupDescription()); + + final String groupProfileUrl = S3UrlGenerator.generateFileName( + groupOwnerId, + dto.groupImageDirectory(), + dto.groupImageProfileFileName() + ); + group.updateGroupProfileUrl(groupProfileUrl); + } + + private void checkGroupOwnership(Long groupOwnerId, Long groupId) { + final Boolean isOwner = memberGroupRepository.findIsOwnerByMemberIdAndGroupId( + groupOwnerId, + groupId) + .orElseThrow(GroupMemberNotFoundException::new); + if (!isOwner) { + throw new NoGroupAuthorityException(); + } + } } 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 index 22643b401..56f4d8b19 100644 --- 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 @@ -26,7 +26,7 @@ public interface GroupMemberCommandApi { """, security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} + tags = {"group member"} ) @ApiResponses(value = { @ApiResponse( @@ -132,7 +132,7 @@ ResponseEntity> rejectGroupInvitation( summary = "그룹원 추방", description = "요청한 사용자가 그룹장인 경우 특정 그룹원을 그룹에서 추방한다.", security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} + tags = {"group member"} ) @ApiResponses(value = { @ApiResponse( 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 index 91c91079b..efe98fb34 100644 --- 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 @@ -143,7 +143,7 @@ public void kickGroupMember( throw new GroupMemberDuplicatedIdException(); } - checkGroupOwnerShip(groupOwnerId, groupId); + checkGroupOwnership(groupOwnerId, groupId); final MemberGroup memberGroup = memberGroupRepository.findMemberGroupByMemberIdAndGroupId( groupMemberId, groupId) @@ -152,7 +152,7 @@ public void kickGroupMember( memberGroupRepository.delete(memberGroup); } - private void checkGroupOwnerShip(Long groupOwnerId, Long groupId) { + private void checkGroupOwnership(Long groupOwnerId, Long groupId) { final Boolean isOwner = memberGroupRepository.findIsOwnerByMemberIdAndGroupId( groupOwnerId, groupId) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3PreSignedUrlManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3PreSignedUrlManager.java index c6e54d65c..6dbe74df9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3PreSignedUrlManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3PreSignedUrlManager.java @@ -22,16 +22,14 @@ public class S3PreSignedUrlManager { private static final String IMAGE_CONTENT_TYPE = "image/png"; private static final String VIDEO_CONTENT_TYPE = "video/mp4"; - private final S3UrlGenerator s3UrlGenerator; private final S3Presigner s3Presigner; private final String temporaryBucketName; private final String bucketName; - public S3PreSignedUrlManager(S3Config s3config, S3UrlGenerator s3UrlGenerator) { + public S3PreSignedUrlManager(S3Config s3config) { this.s3Presigner = s3config.s3Presigner(); this.temporaryBucketName = s3config.getTemporaryBucketName(); this.bucketName = s3config.getBucketName(); - this.s3UrlGenerator = s3UrlGenerator; } /** @@ -103,7 +101,7 @@ private String createS3PreSignedUrlForPut( final String contentType, final String bucketName ) { - final String newFileName = s3UrlGenerator.generateFileName(memberId, directory, fileName); + final String newFileName = S3UrlGenerator.generateFileName(memberId, directory, fileName); final PutObjectPresignRequest putObjectPresignRequest = PutObjectPresignRequest.builder() .signatureDuration(Duration.ofMinutes(PRE_SIGNED_URL_EXPIRATION_TIME)) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java index 541ad8c88..ce63a6ec3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java @@ -7,7 +7,7 @@ @RequiredArgsConstructor public class S3UrlGenerator { - public String generateFileName( + public static String generateFileName( final Long memberId, final String directory, final String fileName From 19635481865fb56647bd947b1a26059b1068fcaf Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 12:50:38 +0900 Subject: [PATCH 036/195] =?UTF-8?q?feat=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dependency/UnitTestDependency.java | 2 +- .../service/GroupCommandServiceTest.java | 77 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java index e1bc6a122..f2cfda28c 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java @@ -18,7 +18,7 @@ public class UnitTestDependency { public static S3PreSignedUrlManager s3PreSignedUrlManager() { S3Config s3Config = new S3Config( new S3Properties("a".repeat(32), "b".repeat(32), "origin", "temporary", "us-east")); - return new S3PreSignedUrlManager(s3Config, new S3UrlGenerator()); + return new S3PreSignedUrlManager(s3Config); } public static GeoTransformManager geoTransformManager() { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java index f7410e847..bbb4e5ba2 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java @@ -24,12 +24,14 @@ 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.GroupUpdateDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; 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.exception.GroupMemberNotFoundException; import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository.MemberGroupRepository; @@ -224,5 +226,80 @@ private List ownerGroupMember() { groupId)).doesNotThrowAnyException(); verify(groupRepository, times(1)).delete(any(Group.class)); } + + @Test + void 그룹원이_아닌_경우_그룹_정보를_업데이트하면_예외가_발생한다() { + //given + Long notGroupMemberId = 1L; + Long groupId = 1L; + GroupUpdateDto groupUpdateDto = getGroupUpdateDto(); + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy( + () -> groupCommandService.updateGroup(notGroupMemberId, groupId, groupUpdateDto)) + .isInstanceOf(GroupMemberNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); + } + + private GroupUpdateDto getGroupUpdateDto() { + return GroupUpdateDto.builder() + .groupName("updated_name") + .groupDescription("updated_description") + .groupImageDirectory("updated_image_directory") + .groupImageProfileFileName("updated_group_image_profile_file_name") + .build(); + } + + @Test + void 그룹장이_아닌_경우_그룹_정보를_업데이트하면_예외가_발생한다() { + //given + Long notGroupOwnerId = 1L; + Long groupId = 1L; + GroupUpdateDto groupUpdateDto = getGroupUpdateDto(); + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.of(Boolean.FALSE)); + + //when + //then + assertThatThrownBy(() -> groupCommandService.updateGroup(notGroupOwnerId, groupId, groupUpdateDto)) + .isInstanceOf(NoGroupAuthorityException.class) + .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); + } + + @Test + void 그룹이_없는_경우_그룹_정보를_업데이트하면_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + GroupUpdateDto groupUpdateDto = getGroupUpdateDto(); + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.of(Boolean.TRUE)); + given(groupRepository.findGroupById(anyLong())).willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> groupCommandService.updateGroup(groupOwnerId, groupId, groupUpdateDto)) + .isInstanceOf(GroupNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 그룹장이_그룹_정보를_업데이트하면_그룹_정보가_업데이트된다() { + //given + Long groupOwnerId = 1L; + Long groupId = 1L; + GroupUpdateDto groupUpdateDto = getGroupUpdateDto(); + given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) + .willReturn(Optional.of(Boolean.TRUE)); + given(groupRepository.findGroupById(anyLong())).willReturn(group()); + + //when + //then + assertThatCode(() -> groupCommandService.updateGroup(groupOwnerId, groupId, + groupUpdateDto)).doesNotThrowAnyException(); + } } From 7e5c4081ae3f3f96d328849f037efc7b98bdb73b Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 13:02:19 +0900 Subject: [PATCH 037/195] =?UTF-8?q?feat=20:=20url=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/command/GroupMemberCommandApiController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 0140576f3..531f384fa 100644 --- 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 @@ -13,7 +13,7 @@ import site.timecapsulearchive.core.global.common.response.SuccessCode; @RestController -@RequestMapping("/group-members") +@RequestMapping("/groups") @RequiredArgsConstructor public class GroupMemberCommandApiController implements GroupMemberCommandApi { @@ -30,7 +30,7 @@ public ResponseEntity> quitGroup( return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } - @PostMapping(value = "/invite/{group_id}/member/{target_id}") + @PostMapping(value = "/{group_id}/member/{target_id}/invite") @Override public ResponseEntity> inviteGroup( @AuthenticationPrincipal final Long memberId, @@ -46,7 +46,7 @@ public ResponseEntity> inviteGroup( ); } - @PostMapping(value = "/accept/{group_id}/member/{target_id}") + @PostMapping(value = "/{group_id}/member/{target_id}/accept") @Override public ResponseEntity> acceptGroupInvitation( @AuthenticationPrincipal final Long memberId, @@ -62,7 +62,7 @@ public ResponseEntity> acceptGroupInvitation( ); } - @DeleteMapping(value = "/reject/{group_id}/member/{target_id}") + @DeleteMapping(value = "/{group_id}/member/{target_id}/reject") public ResponseEntity> rejectGroupInvitation( @AuthenticationPrincipal final Long memberId, @PathVariable("group_id") final Long groupId, From fa2ada0bfb587781f0c656b1baa67ad1f90150cb Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 14:55:39 +0900 Subject: [PATCH 038/195] =?UTF-8?q?fix=20:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/GroupCapsuleOpenQueryRepository.java | 2 +- .../capsule/group_capsule/service/GroupCapsuleService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java index acf0a17a2..d6e6d03f2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java @@ -48,7 +48,7 @@ public int getBatchSize() { ); } - public List findIsOpenedByMemberIdAndCapsuleId(final Long capsuleId) { + public List findIsOpenedByCapsuleId(final Long capsuleId) { return jpaQueryFactory .select(groupCapsuleOpen.isOpened) .from(groupCapsuleOpen) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java index 709c8e2b7..e074314b5 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java @@ -110,7 +110,7 @@ public void openGroupCapsule(final Long memberId, final Long capsuleId) { .orElseThrow(GroupCapsuleOpenNotFoundException::new); groupCapsuleOpen.open(); - List capsuleOpens = groupCapsuleOpenQueryRepository.findIsOpenedByMemberIdAndCapsuleId( + List capsuleOpens = groupCapsuleOpenQueryRepository.findIsOpenedByCapsuleId( capsuleId); boolean allGroupMemberOpened = capsuleOpens.stream() From b07c0ca4b3f26de72667ca04ee2c017ecdc03b9c Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 14:55:51 +0900 Subject: [PATCH 039/195] =?UTF-8?q?fix=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=98=88=EC=99=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capsule/exception/GroupCapsuleOpenException.java | 11 ----------- .../core/global/error/ErrorCode.java | 4 +--- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java deleted file mode 100644 index 3c1f9a59d..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/exception/GroupCapsuleOpenException.java +++ /dev/null @@ -1,11 +0,0 @@ -package site.timecapsulearchive.core.domain.capsule.exception; - -import site.timecapsulearchive.core.global.error.ErrorCode; -import site.timecapsulearchive.core.global.error.exception.BusinessException; - -public class GroupCapsuleOpenException extends BusinessException { - - public GroupCapsuleOpenException() { - super(ErrorCode.NOT_GROUP_CAPSULE_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 929fc369d..0c60db255 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 @@ -52,9 +52,7 @@ public enum ErrorCode { //capsule CAPSULE_NOT_FOUND_ERROR(404, "CAPSULE-001", "캡슐을 찾지 못하였습니다."), NO_CAPSULE_AUTHORITY_ERROR(403, "CAPSULE-002", "캡슐에 접근 권한이 없습니다."), - NOT_GROUP_CAPSULE_ERROR(400, "CAPSULE-003", - "그룹 캡슐이 아니라 그룹 캡슐 개봉을 할 수 없습니다. 비밀, 친구 캡슐 개봉을 사용하세요"), - GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR(404, "CAPSULE-004", "그룹 캡슐 개봉상태를 찾을 수 없습니다."), + GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR(404, "CAPSULE-003", "그룹 캡슐 개봉상태를 찾을 수 없습니다."), //friend FRIEND_NOT_FOUND_ERROR(404, "FRIEND-001", "친구를 찾지 못하였습니다"), From 17d1462af29b472069da082f9e47cccef208a0f1 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 15 May 2024 14:56:15 +0900 Subject: [PATCH 040/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=94=BD=EC=8A=A4=EC=B2=98=EB=A1=9C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/GroupCapsuleOpenFixture.java | 17 +++++++++-- .../service/GroupCapsuleServiceTest.java | 30 ++++--------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java index f555ad68a..795913fd7 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java @@ -1,8 +1,11 @@ package site.timecapsulearchive.core.common.fixture.domain; import java.util.List; +import java.util.Optional; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; +import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; +import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.member.entity.Member; public class GroupCapsuleOpenFixture { @@ -18,8 +21,16 @@ public static List groupCapsuleOpens(Boolean isOpened, Capsule ).toList(); } - public static GroupCapsuleOpen groupCapsuleOpen(Member member, Capsule capsule, - Boolean isOpened) { - return GroupCapsuleOpen.createOf(member, capsule, isOpened); + public static Optional groupCapsuleOpen(int dataPrefix) { + Member member = MemberFixture.member(dataPrefix); + CapsuleSkin capsuleSkin = CapsuleSkinFixture.capsuleSkin(member); + + return Optional.of( + GroupCapsuleOpen.createOf( + MemberFixture.member(dataPrefix), + CapsuleFixture.capsule(member, capsuleSkin, CapsuleType.GROUP), + Boolean.FALSE + ) + ); } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java index f2c95ac23..c6caa072a 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java @@ -13,14 +13,8 @@ import java.util.Optional; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import site.timecapsulearchive.core.common.fixture.domain.CapsuleFixture; -import site.timecapsulearchive.core.common.fixture.domain.CapsuleSkinFixture; import site.timecapsulearchive.core.common.fixture.domain.GroupCapsuleOpenFixture; -import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.CapsuleDtoFixture; -import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; -import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; import site.timecapsulearchive.core.domain.capsule.generic_capsule.data.dto.CapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleRepository; @@ -28,9 +22,7 @@ import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; -import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberSummaryDto; -import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.error.ErrorCode; class GroupCapsuleServiceTest { @@ -225,8 +217,9 @@ class GroupCapsuleServiceTest { Long memberId = 1L; Long capsuleId = 1L; given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) - .willReturn(groupCapsuleOpen()); - given(groupCapsuleOpenQueryRepository.findIsOpenedByMemberIdAndCapsuleId(anyLong())) + + .willReturn(GroupCapsuleOpenFixture.groupCapsuleOpen(memberId.intValue())); + given(groupCapsuleOpenQueryRepository.findIsOpenedByCapsuleId(anyLong())) .willReturn(List.of(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE)); //when @@ -236,27 +229,14 @@ class GroupCapsuleServiceTest { verifyNoInteractions(capsuleRepository); } - private Optional groupCapsuleOpen() { - Member member = MemberFixture.member(0); - CapsuleSkin capsuleSkin = CapsuleSkinFixture.capsuleSkin(member); - - return Optional.of( - GroupCapsuleOpenFixture.groupCapsuleOpen( - MemberFixture.member(0), - CapsuleFixture.capsule(member, capsuleSkin, CapsuleType.GROUP), - Boolean.FALSE - ) - ); - } - @Test void 모든_그룹원이_캡슐을_개봉한_경우_캡슐은_개봉된다() { //given Long memberId = 1L; Long capsuleId = 1L; given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) - .willReturn(groupCapsuleOpen()); - given(groupCapsuleOpenQueryRepository.findIsOpenedByMemberIdAndCapsuleId(anyLong())) + .willReturn(GroupCapsuleOpenFixture.groupCapsuleOpen(memberId.intValue())); + given(groupCapsuleOpenQueryRepository.findIsOpenedByCapsuleId(anyLong())) .willReturn(List.of(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE)); //when From d87d0c424caf7f3eea374964a48108d28eb19948 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 16 May 2024 07:36:16 +0900 Subject: [PATCH 041/195] =?UTF-8?q?fix=20:=20final=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20group=5Fmember=20->=20member=5Fgroup,=20=ED=94=BD=EC=8A=A4?= =?UTF-8?q?=EC=B2=98=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/GroupCommandApiController.java | 8 +-- .../repository/GroupQueryRepositoryImpl.java | 2 +- .../service/command/GroupCommandService.java | 26 ++++---- .../core/domain/member/entity/Member.java | 2 +- .../repository/MemberQueryRepository.java | 2 +- .../api/command/MemberGroupCommandApi.java} | 16 +++-- .../MemberGroupCommandApiController.java} | 18 +++--- .../data/GroupOwnerSummaryDto.java | 2 +- .../entity/GroupInvite.java | 2 +- .../entity/MemberGroup.java | 2 +- .../GroupInviteNotFoundException.java | 2 +- .../exception/GroupQuitException.java | 2 +- ...MemberGroupKickDuplicatedIdException.java} | 6 +- .../MemberGroupNotFoundException.java} | 6 +- .../exception/NoGroupAuthorityException.java | 2 +- .../GroupInviteQueryRepository.java | 2 +- .../GroupInviteQueryRepositoryImpl.java | 6 +- .../GroupInviteRepository.java | 4 +- .../MemberGroupQueryRepository.java | 4 +- .../MemberGroupQueryRepositoryImpl.java | 6 +- .../MemberGroupRepository.java | 4 +- .../service/MemberGroupCommandService.java} | 32 +++++----- .../fixture/domain/MemberGroupFixture.java | 62 ++++++++++++++++++- .../common/fixture/dto/GroupDtoFixture.java | 2 +- .../GroupCapsuleQueryRepositoryTest.java | 2 +- .../MemberGroupQueryRepositoryTest.java | 2 +- .../service/GroupCommandServiceTest.java | 42 +++---------- .../MemberGroupCommandServiceTest.java} | 53 ++++++---------- 28 files changed, 167 insertions(+), 152 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member/api/command/GroupMemberCommandApi.java => member_group/api/command/MemberGroupCommandApi.java} (93%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member/api/command/GroupMemberCommandApiController.java => member_group/api/command/MemberGroupCommandApiController.java} (81%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/data/GroupOwnerSummaryDto.java (64%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/entity/GroupInvite.java (96%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/entity/MemberGroup.java (96%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/exception/GroupInviteNotFoundException.java (83%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/exception/GroupQuitException.java (81%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member/exception/GroupMemberDuplicatedIdException.java => member_group/exception/MemberGroupKickDuplicatedIdException.java} (52%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member/exception/GroupMemberNotFoundException.java => member_group/exception/MemberGroupNotFoundException.java} (55%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/exception/NoGroupAuthorityException.java (82%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/repository/groupInviteRepository/GroupInviteQueryRepository.java (81%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java (90%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/repository/groupInviteRepository/GroupInviteRepository.java (85%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/repository/memberGroupRepository/MemberGroupQueryRepository.java (70%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java (90%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member => member_group}/repository/memberGroupRepository/MemberGroupRepository.java (79%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/{group_member/service/GroupMemberCommandService.java => member_group/service/MemberGroupCommandService.java} (85%) rename backend/core/src/test/java/site/timecapsulearchive/core/domain/{group_member/service/GroupMemberCommandServiceTest.java => member_group/service/MemberGroupCommandServiceTest.java} (88%) 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 index 7e318cccf..ee9b56a82 100644 --- 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 @@ -31,7 +31,7 @@ public class GroupCommandApiController implements GroupCommandApi { @PostMapping public ResponseEntity> createGroup( @AuthenticationPrincipal final Long memberId, - @Valid @RequestBody GroupCreateRequest request + @Valid @RequestBody final GroupCreateRequest request ) { final String groupProfileUrl = s3UrlGenerator.generateFileName(memberId, request.groupDirectory(), request.groupImage()); @@ -60,9 +60,9 @@ public ResponseEntity> deleteGroupById( @PatchMapping(value = "/{group_id}", consumes = {"application/json"}) @Override public ResponseEntity> updateGroupById( - @AuthenticationPrincipal Long memberId, - @PathVariable("group_id") Long groupId, - @RequestBody GroupUpdateRequest request + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId, + @Valid @RequestBody final GroupUpdateRequest request ) { groupCommandService.updateGroup(memberId, groupId, request.toDto()); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index f24ce647b..9a29abc53 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -3,7 +3,7 @@ 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_member.entity.QMemberGroup.memberGroup; +import static site.timecapsulearchive.core.domain.member_group.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/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index f12106bbd..113ba2a7c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -14,11 +14,11 @@ import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; -import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; -import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberNotFoundException; -import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; -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_group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.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; @@ -84,7 +84,7 @@ public void deleteGroup(final Long memberId, final Long groupId) { checkGroupMemberExist(groupMembers); checkGroupCapsuleExist(groupId); - List groupInviteIds = groupInviteRepository.findGroupInviteIdsByGroupIdAndGroupOwnerId( + final List groupInviteIds = groupInviteRepository.findGroupInviteIdsByGroupIdAndGroupOwnerId( groupId, memberId); groupInviteRepository.bulkDelete(groupInviteIds); @@ -95,7 +95,7 @@ public void deleteGroup(final Long memberId, final Long groupId) { s3ObjectManager.deleteObject(groupProfilePath); } - private void checkGroupOwnership(Long memberId, List groupMembers) { + private void checkGroupOwnership(final Long memberId, final List groupMembers) { final boolean isGroupOwner = groupMembers.stream() .anyMatch(mg -> mg.getMember().getId().equals(memberId) && mg.getIsOwner()); if (!isGroupOwner) { @@ -103,7 +103,7 @@ private void checkGroupOwnership(Long memberId, List groupMembers) } } - private void checkGroupMemberExist(List groupMembers) { + private void checkGroupMemberExist(final List groupMembers) { final boolean groupMemberExist = groupMembers.size() > 1; if (groupMemberExist) { throw new GroupDeleteFailException(ErrorCode.GROUP_MEMBER_EXIST_ERROR); @@ -111,7 +111,7 @@ private void checkGroupMemberExist(List groupMembers) { groupMembers.forEach(memberGroupRepository::delete); } - private void checkGroupCapsuleExist(Long groupId) { + private void checkGroupCapsuleExist(final Long groupId) { final boolean groupCapsuleExist = groupCapsuleQueryRepository.findGroupCapsuleExistByGroupId( groupId); if (groupCapsuleExist) { @@ -131,10 +131,10 @@ private void checkGroupCapsuleExist(Long groupId) { * @param dto 업데이트할 그룹 정보 */ @Transactional - public void updateGroup(Long groupOwnerId, Long groupId, GroupUpdateDto dto) { + public void updateGroup(final Long groupOwnerId, final Long groupId, final GroupUpdateDto dto) { checkGroupOwnership(groupOwnerId, groupId); - Group group = groupRepository.findGroupById(groupId) + final Group group = groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); group.updateGroupName(dto.groupName()); @@ -148,11 +148,11 @@ public void updateGroup(Long groupOwnerId, Long groupId, GroupUpdateDto dto) { group.updateGroupProfileUrl(groupProfileUrl); } - private void checkGroupOwnership(Long groupOwnerId, Long groupId) { + private void checkGroupOwnership(final Long groupOwnerId, final Long groupId) { final Boolean isOwner = memberGroupRepository.findIsOwnerByMemberIdAndGroupId( groupOwnerId, groupId) - .orElseThrow(GroupMemberNotFoundException::new); + .orElseThrow(MemberGroupNotFoundException::new); if (!isOwner) { throw new NoGroupAuthorityException(); } 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 432cb03d1..c8da6e86d 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_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.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 cb36a5ec6..d7daa0bc8 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_member.entity.QMemberGroup.memberGroup; +import static site.timecapsulearchive.core.domain.member_group.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; 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/member_group/api/command/MemberGroupCommandApi.java similarity index 93% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApi.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java index 56f4d8b19..58c5566f2 100644 --- 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/member_group/api/command/MemberGroupCommandApi.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.api.command; +package site.timecapsulearchive.core.domain.member_group.api.command; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -9,12 +9,10 @@ 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; import site.timecapsulearchive.core.global.error.ErrorResponse; -public interface GroupMemberCommandApi { +public interface MemberGroupCommandApi { @Operation( summary = "그룹 탈퇴", @@ -26,7 +24,7 @@ public interface GroupMemberCommandApi { """, security = {@SecurityRequirement(name = "user_token")}, - tags = {"group member"} + tags = {"member group"} ) @ApiResponses(value = { @ApiResponse( @@ -59,7 +57,7 @@ ResponseEntity> quitGroup( summary = "그룹 요청", description = "그룹장인 경우 친구에게 그룹 가입 요청을 할 수 있다.", security = {@SecurityRequirement(name = "user_token")}, - tags = {"group member"} + tags = {"member group"} ) @ApiResponses(value = { @ApiResponse( @@ -81,7 +79,7 @@ ResponseEntity> inviteGroup( summary = "그룹 요청 수락", description = "특정 그룹으로부터 그룹 요청을 수락한다.", security = {@SecurityRequirement(name = "user_token")}, - tags = {"group member"} + tags = {"member group"} ) @ApiResponses(value = { @ApiResponse( @@ -111,7 +109,7 @@ ResponseEntity> acceptGroupInvitation( summary = "그룹 요청 거부", description = "특정 그룹으로부터 초대 요청을 거부한다.", security = {@SecurityRequirement(name = "user_token")}, - tags = {"group member"} + tags = {"member group"} ) @ApiResponses(value = { @ApiResponse( @@ -132,7 +130,7 @@ ResponseEntity> rejectGroupInvitation( summary = "그룹원 추방", description = "요청한 사용자가 그룹장인 경우 특정 그룹원을 그룹에서 추방한다.", security = {@SecurityRequirement(name = "user_token")}, - tags = {"group member"} + tags = {"member group"} ) @ApiResponses(value = { @ApiResponse( 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/member_group/api/command/MemberGroupCommandApiController.java similarity index 81% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/api/command/GroupMemberCommandApiController.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java index 531f384fa..0d497a132 100644 --- 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/member_group/api/command/MemberGroupCommandApiController.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.api.command; +package site.timecapsulearchive.core.domain.member_group.api.command; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -8,16 +8,16 @@ 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.domain.member_group.service.MemberGroupCommandService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @RestController @RequestMapping("/groups") @RequiredArgsConstructor -public class GroupMemberCommandApiController implements GroupMemberCommandApi { +public class MemberGroupCommandApiController implements MemberGroupCommandApi { - private final GroupMemberCommandService groupMemberCommandService; + private final MemberGroupCommandService MemberGroupCommandService; @DeleteMapping(value = "/{group_id}/members/quit") @Override @@ -25,7 +25,7 @@ public ResponseEntity> quitGroup( @AuthenticationPrincipal final Long memberId, @PathVariable("group_id") final Long groupId ) { - groupMemberCommandService.quitGroup(memberId, groupId); + MemberGroupCommandService.quitGroup(memberId, groupId); return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } @@ -37,7 +37,7 @@ public ResponseEntity> inviteGroup( @PathVariable("group_id") final Long groupId, @PathVariable("target_id") final Long targetId ) { - groupMemberCommandService.inviteGroup(memberId, groupId, targetId); + MemberGroupCommandService.inviteGroup(memberId, groupId, targetId); return ResponseEntity.ok( ApiSpec.empty( @@ -53,7 +53,7 @@ public ResponseEntity> acceptGroupInvitation( @PathVariable("group_id") final Long groupId, @PathVariable("target_id") final Long targetId ) { - groupMemberCommandService.acceptGroupInvite(memberId, groupId, targetId); + MemberGroupCommandService.acceptGroupInvite(memberId, groupId, targetId); return ResponseEntity.ok( ApiSpec.empty( @@ -68,7 +68,7 @@ public ResponseEntity> rejectGroupInvitation( @PathVariable("group_id") final Long groupId, @PathVariable("target_id") final Long targetId) { - groupMemberCommandService.rejectRequestGroup(memberId, groupId, targetId); + MemberGroupCommandService.rejectRequestGroup(memberId, groupId, targetId); return ResponseEntity.ok( ApiSpec.empty( @@ -84,7 +84,7 @@ public ResponseEntity> kickGroupMember( @PathVariable("group_id") final Long groupId, @PathVariable("group_member_id") final Long groupMemberId ) { - groupMemberCommandService.kickGroupMember(memberId, groupId, groupMemberId); + MemberGroupCommandService.kickGroupMember(memberId, groupId, groupMemberId); return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/data/GroupOwnerSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/GroupOwnerSummaryDto.java similarity index 64% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/data/GroupOwnerSummaryDto.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/GroupOwnerSummaryDto.java index cbe65a301..21a19f609 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/data/GroupOwnerSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/GroupOwnerSummaryDto.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.data; +package site.timecapsulearchive.core.domain.member_group.data; public record GroupOwnerSummaryDto( String nickname, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/GroupInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java similarity index 96% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/GroupInvite.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java index 02a39f238..dc67cce81 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/GroupInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.entity; +package site.timecapsulearchive.core.domain.member_group.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/MemberGroup.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/MemberGroup.java similarity index 96% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/MemberGroup.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/MemberGroup.java index c1c8b3cec..50df56c0f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/entity/MemberGroup.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/MemberGroup.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.entity; +package site.timecapsulearchive.core.domain.member_group.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupInviteNotFoundException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupInviteNotFoundException.java similarity index 83% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupInviteNotFoundException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupInviteNotFoundException.java index 41201b335..820b16d82 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupInviteNotFoundException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupInviteNotFoundException.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.exception; +package site.timecapsulearchive.core.domain.member_group.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_member/exception/GroupQuitException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupQuitException.java similarity index 81% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupQuitException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupQuitException.java index 88c00a51c..2bb05ef4c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupQuitException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupQuitException.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.exception; +package site.timecapsulearchive.core.domain.member_group.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_member/exception/GroupMemberDuplicatedIdException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupKickDuplicatedIdException.java similarity index 52% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberDuplicatedIdException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupKickDuplicatedIdException.java index 6fb29d827..7e1fea6c4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberDuplicatedIdException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupKickDuplicatedIdException.java @@ -1,11 +1,11 @@ -package site.timecapsulearchive.core.domain.group_member.exception; +package site.timecapsulearchive.core.domain.member_group.exception; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.BusinessException; -public class GroupMemberDuplicatedIdException extends BusinessException { +public class MemberGroupKickDuplicatedIdException extends BusinessException { - public GroupMemberDuplicatedIdException() { + public MemberGroupKickDuplicatedIdException() { super(ErrorCode.GROUP_MEMBER_DUPLICATED_ID_ERROR); } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberNotFoundException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupNotFoundException.java similarity index 55% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberNotFoundException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupNotFoundException.java index 364c387ca..dafdd6f4c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/GroupMemberNotFoundException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupNotFoundException.java @@ -1,11 +1,11 @@ -package site.timecapsulearchive.core.domain.group_member.exception; +package site.timecapsulearchive.core.domain.member_group.exception; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.BusinessException; -public class GroupMemberNotFoundException extends BusinessException { +public class MemberGroupNotFoundException extends BusinessException { - public GroupMemberNotFoundException() { + public MemberGroupNotFoundException() { super(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR); } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/NoGroupAuthorityException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/NoGroupAuthorityException.java similarity index 82% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/NoGroupAuthorityException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/NoGroupAuthorityException.java index fcbd129ed..47381a483 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/exception/NoGroupAuthorityException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/NoGroupAuthorityException.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.exception; +package site.timecapsulearchive.core.domain.member_group.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_member/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java similarity index 81% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java index de9bbc52d..ecb965fd2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository; import java.util.List; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java similarity index 90% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 6e1c973dd..070926508 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -1,7 +1,7 @@ -package site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository; -import static site.timecapsulearchive.core.domain.group_member.entity.QGroupInvite.groupInvite; +import static site.timecapsulearchive.core.domain.member_group.entity.QGroupInvite.groupInvite; import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; @@ -23,7 +23,7 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepositor private final JPAQueryFactory jpaQueryFactory; @Override - public void bulkSave(Long groupOwnerId, List groupMemberIds) { + public void bulkSave(final Long groupOwnerId, final List groupMemberIds) { jdbcTemplate.batchUpdate( """ INSERT INTO group_invite ( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java similarity index 85% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java index f411caa1a..e429c0346 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/groupInviteRepository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java @@ -1,11 +1,11 @@ -package site.timecapsulearchive.core.domain.group_member.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.member_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_member.entity.GroupInvite; +import site.timecapsulearchive.core.domain.member_group.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/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java similarity index 70% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java index 993d9b185..f21112a0a 100644 --- 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/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -1,7 +1,7 @@ -package site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; import java.util.Optional; -import site.timecapsulearchive.core.domain.group_member.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; public interface MemberGroupQueryRepository { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java similarity index 90% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index a94974b61..243066464 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -1,7 +1,7 @@ -package site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; -import static site.timecapsulearchive.core.domain.group_member.entity.QMemberGroup.memberGroup; +import static site.timecapsulearchive.core.domain.member_group.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_member.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; @Repository @RequiredArgsConstructor diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java similarity index 79% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java index 7155578dd..2d42643b7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/repository/memberGroupRepository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java @@ -1,9 +1,9 @@ -package site.timecapsulearchive.core.domain.group_member.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; import java.util.List; import java.util.Optional; import org.springframework.data.repository.Repository; -import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.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/member_group/service/MemberGroupCommandService.java similarity index 85% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandService.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index efe98fb34..fe238958a 100644 --- 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/member_group/service/MemberGroupCommandService.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.service; +package site.timecapsulearchive.core.domain.member_group.service; import java.util.List; import lombok.RequiredArgsConstructor; @@ -10,16 +10,16 @@ import site.timecapsulearchive.core.domain.group.entity.Group; 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.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_member.exception.GroupMemberDuplicatedIdException; -import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberNotFoundException; -import site.timecapsulearchive.core.domain.group_member.exception.GroupQuitException; -import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; -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_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; +import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.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; @@ -28,7 +28,7 @@ @Service @RequiredArgsConstructor -public class GroupMemberCommandService { +public class MemberGroupCommandService { private final MemberRepository memberRepository; private final GroupRepository groupRepository; @@ -114,7 +114,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { public void quitGroup(final Long memberId, final Long groupId) { final MemberGroup groupMember = memberGroupRepository.findMemberGroupByMemberIdAndGroupId( memberId, groupId) - .orElseThrow(GroupMemberNotFoundException::new); + .orElseThrow(MemberGroupNotFoundException::new); if (groupMember.getIsOwner()) { throw new GroupQuitException(ErrorCode.GROUP_OWNER_QUIT_ERROR); } @@ -140,14 +140,14 @@ public void kickGroupMember( final Long groupMemberId ) { if (groupOwnerId.equals(groupMemberId)) { - throw new GroupMemberDuplicatedIdException(); + throw new MemberGroupKickDuplicatedIdException(); } checkGroupOwnership(groupOwnerId, groupId); final MemberGroup memberGroup = memberGroupRepository.findMemberGroupByMemberIdAndGroupId( groupMemberId, groupId) - .orElseThrow(GroupMemberNotFoundException::new); + .orElseThrow(MemberGroupNotFoundException::new); memberGroupRepository.delete(memberGroup); } @@ -156,7 +156,7 @@ private void checkGroupOwnership(Long groupOwnerId, Long groupId) { final Boolean isOwner = memberGroupRepository.findIsOwnerByMemberIdAndGroupId( groupOwnerId, groupId) - .orElseThrow(GroupMemberNotFoundException::new); + .orElseThrow(MemberGroupNotFoundException::new); if (!isOwner) { throw new NoGroupAuthorityException(); } 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 68b03fbaf..137523db4 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 @@ -2,9 +2,11 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.group_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member.entity.Member; public class MemberGroupFixture { @@ -61,4 +63,62 @@ public static List memberGroups(List members, Group group) .map(m -> memberGroup(m, group, Boolean.FALSE)) .toList(); } + + /** + * 임의로 그룹원들과 그룹장만 만든다. + * @return 그룹원들과 그룹장 {@code List} + */ + public static List memberGroupsWithOwner() { + 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; + } + + /** + * 임의로 그룹원들만 만든다. + * @return 그룹원들 {@code List} + */ + public static List memberGroupsWithoutOwner() { + return List.of( + MemberGroupFixture.memberGroup( + MemberFixture.memberWithMemberId(1), + GroupFixture.group(), + false + ) + ); + } + + /** + * 테스트픽스처 - 임의로 그룹원을 만든다. + * @return 그룹원인 {@code MemberGroup} + */ + public static Optional notOwner() { + return Optional.of( + MemberGroupFixture.memberGroup( + MemberFixture.memberWithMemberId(1L), + GroupFixture.group(), + Boolean.FALSE + ) + ); + } + + /** + * 테스트픽스처 - 임의로 그룹장을 만든다. + * @return 그룹장인 {@code MemberGroup} + */ + public static Optional owner() { + return Optional.of( + MemberGroupFixture.groupOwner(MemberFixture.memberWithMemberId(1L), + GroupFixture.group()) + ); + } } 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 ffe3c0252..e14b0c1d9 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_member.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.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 0a9d98dfe..bc72de543 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_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.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 c09ecd314..d037249b6 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,7 +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_member.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.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/GroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java index bbb4e5ba2..c6a32a190 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java @@ -12,7 +12,6 @@ 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; @@ -30,11 +29,11 @@ import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; 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.exception.GroupMemberNotFoundException; -import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; -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_group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.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; @@ -120,7 +119,7 @@ class GroupCommandServiceTest { given(groupRepository.findGroupById(anyLong())).willReturn(group()); given(memberGroupRepository.findMemberGroupsByGroupId(groupId)).willReturn( - notOwnerGroupMember()); + MemberGroupFixture.memberGroupsWithoutOwner()); //when //then @@ -129,16 +128,6 @@ class GroupCommandServiceTest { .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); } - private List notOwnerGroupMember() { - return List.of( - MemberGroupFixture.memberGroup( - MemberFixture.memberWithMemberId(1), - GroupFixture.group(), - false - ) - ); - } - @Test void 그룹원이_존재하는_그룹_아이디로_삭제를_시도하면_예외가_발생한다() { //given @@ -147,7 +136,7 @@ private List notOwnerGroupMember() { given(groupRepository.findGroupById(anyLong())).willReturn(group()); given(memberGroupRepository.findMemberGroupsByGroupId(groupMemberExistGroupId)).willReturn( - groupMembers()); + MemberGroupFixture.memberGroupsWithOwner()); //when //then @@ -163,21 +152,6 @@ private Optional 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 @@ -240,7 +214,7 @@ private List ownerGroupMember() { //then assertThatThrownBy( () -> groupCommandService.updateGroup(notGroupMemberId, groupId, groupUpdateDto)) - .isInstanceOf(GroupMemberNotFoundException.class) + .isInstanceOf(MemberGroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); } 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/member_group/service/MemberGroupCommandServiceTest.java similarity index 88% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/group_member/service/GroupMemberCommandServiceTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 0b41474cf..284a7eb5f 100644 --- 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/member_group/service/MemberGroupCommandServiceTest.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group_member.service; +package site.timecapsulearchive.core.domain.member_group.service; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -21,21 +21,21 @@ 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.entity.MemberGroup; -import site.timecapsulearchive.core.domain.group_member.exception.GroupInviteNotFoundException; -import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberDuplicatedIdException; -import site.timecapsulearchive.core.domain.group_member.exception.GroupMemberNotFoundException; -import site.timecapsulearchive.core.domain.group_member.exception.GroupQuitException; -import site.timecapsulearchive.core.domain.group_member.exception.NoGroupAuthorityException; -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_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; +import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; +import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.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 { +public class MemberGroupCommandServiceTest { private final MemberRepository memberRepository = mock(MemberRepository.class); private final GroupRepository groupRepository = mock(GroupRepository.class); @@ -45,7 +45,7 @@ public class GroupMemberCommandServiceTest { SocialNotificationManager.class); private final TransactionTemplate transactionTemplate = TestTransactionTemplate.spied(); - private final GroupMemberCommandService groupMemberCommandService = new GroupMemberCommandService( + private final MemberGroupCommandService groupMemberCommandService = new MemberGroupCommandService( memberRepository, groupRepository, memberGroupRepository, @@ -208,7 +208,7 @@ public class GroupMemberCommandServiceTest { Long groupOwnerId = 1L; Long groupId = 1L; given(memberGroupRepository.findMemberGroupByMemberIdAndGroupId(groupOwnerId, - groupId)).willReturn(ownerGroupMemberOnly()); + groupId)).willReturn(MemberGroupFixture.owner()); //when //then @@ -217,20 +217,13 @@ public class GroupMemberCommandServiceTest { .hasMessageContaining(ErrorCode.GROUP_OWNER_QUIT_ERROR.getMessage()); } - private Optional ownerGroupMemberOnly() { - return Optional.of( - MemberGroupFixture.groupOwner(MemberFixture.memberWithMemberId(1L), - GroupFixture.group()) - ); - } - @Test void 그룹장이_아닌_사용자가_그룹_탈퇴를_시도하면_그룹을_탈퇴한다() { //given Long groupOwnerId = 1L; Long groupId = 1L; given(memberGroupRepository.findMemberGroupByMemberIdAndGroupId(groupOwnerId, - groupId)).willReturn(notOwnerGroupMemberOnly()); + groupId)).willReturn(MemberGroupFixture.notOwner()); //when groupMemberCommandService.quitGroup(groupOwnerId, groupId); @@ -239,16 +232,6 @@ private Optional ownerGroupMemberOnly() { verify(memberGroupRepository, times(1)).delete(any(MemberGroup.class)); } - private Optional notOwnerGroupMemberOnly() { - return Optional.of( - MemberGroupFixture.memberGroup( - MemberFixture.memberWithMemberId(2L), - GroupFixture.group(), - false - ) - ); - } - @Test void 나_자신을_그룹에서_삭제하려하면_예외가_발생한다() { //given @@ -259,7 +242,7 @@ private Optional notOwnerGroupMemberOnly() { //then assertThatThrownBy( () -> groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, groupOwnerId)) - .isInstanceOf(GroupMemberDuplicatedIdException.class) + .isInstanceOf(MemberGroupKickDuplicatedIdException.class) .hasMessageContaining(ErrorCode.GROUP_MEMBER_DUPLICATED_ID_ERROR.getMessage()); } @@ -276,7 +259,7 @@ private Optional notOwnerGroupMemberOnly() { //then assertThatThrownBy( () -> groupMemberCommandService.kickGroupMember(notExistGroupOwnerId, groupId, groupMemberId)) - .isInstanceOf(GroupMemberNotFoundException.class) + .isInstanceOf(MemberGroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); } @@ -312,7 +295,7 @@ private Optional notOwnerGroupMemberOnly() { //then assertThatThrownBy( () -> groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, notExistGroupMemberId)) - .isInstanceOf(GroupMemberNotFoundException.class) + .isInstanceOf(MemberGroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); } @@ -325,7 +308,7 @@ private Optional notOwnerGroupMemberOnly() { given(memberGroupRepository.findIsOwnerByMemberIdAndGroupId(anyLong(), anyLong())) .willReturn(Optional.of(Boolean.TRUE)); given(memberGroupRepository.findMemberGroupByMemberIdAndGroupId(anyLong(), anyLong())) - .willReturn(notOwnerGroupMemberOnly()); + .willReturn(MemberGroupFixture.notOwner()); //when groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, groupMemberId); From 501fde810647a6171589f216415e8f240e4ab94a Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 16 May 2024 23:26:38 +0900 Subject: [PATCH 042/195] =?UTF-8?q?fix=20:=20swagger=20api=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/api/command/GroupCommandApi.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 index 46b4e690f..70cf97f74 100644 --- 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 @@ -84,6 +84,12 @@ ResponseEntity> createGroup( GroupCreateRequest request ); + @Operation( + summary = "그룹 수정", + description = "그룹장인 경우에 그룹의 기본 정보들을 수정한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) @ApiResponses(value = { @ApiResponse( responseCode = "200", From bf8c7ac9d6e10f5bac541ec251b13ec2108d99e0 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 20 May 2024 22:12:28 +0900 Subject: [PATCH 043/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=9D=B8=EC=9B=90=EC=9D=84=2030=EB=AA=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/api/command/GroupCommandApiController.java | 3 +-- .../domain/group/data/reqeust/GroupCreateRequest.java | 2 ++ .../core/infra/s3/manager/S3UrlGenerator.java | 8 +++----- 3 files changed, 6 insertions(+), 7 deletions(-) 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 index ee9b56a82..17b23d55d 100644 --- 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 @@ -25,7 +25,6 @@ public class GroupCommandApiController implements GroupCommandApi { private final GroupCommandService groupCommandService; - private final S3UrlGenerator s3UrlGenerator; @Override @PostMapping @@ -33,7 +32,7 @@ public ResponseEntity> createGroup( @AuthenticationPrincipal final Long memberId, @Valid @RequestBody final GroupCreateRequest request ) { - final String groupProfileUrl = s3UrlGenerator.generateFileName(memberId, + final String groupProfileUrl = S3UrlGenerator.generateFileName(memberId, request.groupDirectory(), request.groupImage()); final GroupCreateDto dto = request.toDto(groupProfileUrl); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupCreateRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupCreateRequest.java index 6376b4815..06cbc5817 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupCreateRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/reqeust/GroupCreateRequest.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.List; import site.timecapsulearchive.core.domain.group.data.dto.GroupCreateDto; import site.timecapsulearchive.core.global.common.valid.annotation.Image; @@ -28,6 +29,7 @@ public record GroupCreateRequest( @Schema(description = "그룹원 아이디들") @NotNull(message = "그룹원 아이디들은 필수 입니다.") + @Size(max = 30, message = "그룹 초대원은 최대 30명까지 입니다.") List targetIds ) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java index ce63a6ec3..bea1b9fe8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/s3/manager/S3UrlGenerator.java @@ -1,11 +1,9 @@ package site.timecapsulearchive.core.infra.s3.manager; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; +public final class S3UrlGenerator { -@Component -@RequiredArgsConstructor -public class S3UrlGenerator { + private S3UrlGenerator() { + } public static String generateFileName( final Long memberId, From ed7d945c04ebb3423a01d6a22ab2321d5a626e12 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 20 May 2024 23:06:26 +0900 Subject: [PATCH 044/195] =?UTF-8?q?fix=20:=20friend=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CapsuleQueryRepository.java | 2 +- .../service/CapsuleService.java | 8 +- .../group_capsule/api/GroupCapsuleApi.java | 4 +- .../service/GroupCapsuleService.java | 4 +- .../FriendCommandApi.java} | 118 ++---------- .../command/FriendCommandApiController.java | 100 ++++++++++ .../friend/api/query/FriendQueryApi.java | 100 ++++++++++ .../FriendQueryApiController.java} | 98 +--------- .../domain/friend/facade/FriendFacade.java | 43 ----- .../FriendInviteQueryRepository.java | 8 + .../FriendInviteQueryRepositoryImpl.java} | 8 +- .../FriendInviteRepository.java | 5 +- .../MemberFriendQueryRepository.java | 37 ++++ .../MemberFriendQueryRepositoryImpl.java} | 4 +- .../MemberFriendRepository.java | 5 +- .../FriendCommandService.java} | 88 +++------ .../service/query/FriendQueryService.java | 57 ++++++ .../group/api/command/GroupCommandApi.java | 13 +- .../repository/GroupQueryRepositoryImpl.java | 2 +- .../service/command/GroupCommandService.java | 6 +- .../core/domain/member/entity/Member.java | 2 +- .../repository/MemberQueryRepository.java | 155 ++-------------- .../repository/MemberQueryRepositoryImpl.java | 174 ++++++++++++++++++ .../member/repository/MemberRepository.java | 2 +- .../domain/member/service/MemberService.java | 22 +-- .../api/command/MemberGroupCommandApi.java | 3 +- .../MemberGroupQueryRepositoryImpl.java | 2 +- .../service/MemberGroupCommandService.java | 10 +- .../common/dependency/UnitTestDependency.java | 1 - .../common/fixture/domain/CapsuleFixture.java | 9 +- .../fixture/domain/MemberGroupFixture.java | 16 +- .../common/fixture/dto/CapsuleDtoFixture.java | 7 +- .../GroupCapsuleQueryRepositoryTest.java | 2 +- .../PublicCapsuleQueryRepositoryTest.java | 1 - .../service/PublicCapsuleServiceTest.java | 1 - .../GroupCapsuleOpenQueryRepositoryTest.java | 3 +- .../FriendInviteQueryRepositoryTest.java | 4 +- .../MemberFriendQueryRepositoryTest.java | 7 +- .../MemberFriendRepositoryTest.java | 1 + ...eTest.java => FriendQueryServiceTest.java} | 50 ++--- .../MemberGroupQueryRepositoryTest.java | 2 +- .../service/GroupCommandServiceTest.java | 10 +- .../repository/MemberQueryRepositoryTest.java | 2 +- .../member/service/MemberServiceTest.java | 20 +- .../MemberGroupCommandServiceTest.java | 15 +- 45 files changed, 663 insertions(+), 568 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/{FriendApi.java => command/FriendCommandApi.java} (58%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/{FriendApiController.java => query/FriendQueryApiController.java} (57%) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/facade/FriendFacade.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/{FriendInviteQueryRepository.java => friend_invite/FriendInviteQueryRepositoryImpl.java} (86%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/{ => friend_invite}/FriendInviteRepository.java (89%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/{MemberFriendQueryRepository.java => member_friend/MemberFriendQueryRepositoryImpl.java} (97%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/{ => member_friend}/MemberFriendRepository.java (85%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/{FriendService.java => command/FriendCommandService.java} (64%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java rename backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/{FriendServiceTest.java => FriendQueryServiceTest.java} (59%) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java index 505e9371e..889c02f6d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java @@ -56,7 +56,7 @@ public List findARCapsuleSummaryDtosByCurrentLocation .join(capsule.capsuleSkin, capsuleSkin) .join(capsule.member, member) .where(ST_Contains(mbr, capsule.point).and(capsule.member.id.eq(memberId) - .and(eqCapsuleType(capsuleType)))) + .and(eqCapsuleType(capsuleType)))) .fetch(); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java index 150af31c1..960fa1c4c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/service/CapsuleService.java @@ -16,7 +16,7 @@ import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleQueryRepository; import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleRepository; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; -import site.timecapsulearchive.core.domain.friend.repository.MemberFriendQueryRepository; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.geography.GeoTransformManager; @@ -27,7 +27,7 @@ public class CapsuleService { private final CapsuleQueryRepository capsuleQueryRepository; private final CapsuleRepository capsuleRepository; - private final MemberFriendQueryRepository memberFriendQueryRepository; + private final MemberFriendRepository memberFriendRepository; private final GeoTransformManager geoTransformManager; /** @@ -114,7 +114,7 @@ public List findFriendsCapsulesByCurrentLocation( final Polygon mbr = geoTransformManager.getDistanceMBROf3857(point, dto.distance()); - final List friendIds = memberFriendQueryRepository.findFriendIdsByOwnerId(memberId); + final List friendIds = memberFriendRepository.findFriendIdsByOwnerId(memberId); return capsuleQueryRepository.findFriendsCapsuleSummaryDtosByCurrentLocationAndCapsuleType( friendIds, @@ -138,7 +138,7 @@ public List findFriendsARCapsulesByCurrentLocation( final Polygon mbr = geoTransformManager.getDistanceMBROf3857(point, dto.distance()); - final List friendIds = memberFriendQueryRepository.findFriendIdsByOwnerId(memberId); + final List friendIds = memberFriendRepository.findFriendIdsByOwnerId(memberId); return capsuleQueryRepository.findFriendsARCapsulesByCurrentLocation( friendIds, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java index d1647f8d7..7a5371a4f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java @@ -156,8 +156,8 @@ ResponseEntity> getMyGroupCapsules( @Operation( summary = "그룹 캡슐 개봉", description = """ - 그룹원이 그룹 캡슐을 개봉한다.
캡슐을 만들 때의 그룹원 모두가 캡슐을 개봉해야만 캡슐이 개봉된다. - """, + 그룹원이 그룹 캡슐을 개봉한다.
캡슐을 만들 때의 그룹원 모두가 캡슐을 개봉해야만 캡슐이 개봉된다. + """, security = {@SecurityRequirement(name = "user_token")}, tags = {"group capsule"} ) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java index e074314b5..d40189ad3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java @@ -105,8 +105,8 @@ public Slice findMyGroupCapsuleSlice( @Transactional public void openGroupCapsule(final Long memberId, final Long capsuleId) { GroupCapsuleOpen groupCapsuleOpen = groupCapsuleOpenRepository.findByMemberIdAndCapsuleId( - memberId, - capsuleId) + memberId, + capsuleId) .orElseThrow(GroupCapsuleOpenNotFoundException::new); groupCapsuleOpen.open(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApi.java similarity index 58% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApi.java index e42ccca02..83f1351fe 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApi.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.api; +package site.timecapsulearchive.core.domain.friend.api.command; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -8,61 +8,38 @@ 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.friend.data.request.SearchFriendsRequest; import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; -import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; -import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; -import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; -import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.error.ErrorResponse; -public interface FriendApi { +public interface FriendCommandApi { @Operation( - summary = "소셜 친구 목록 조회", - description = "사용자의 소셜 친구 목록을 보여준다.", + summary = "소셜 친구 요청 수락", + description = "상대방으로부터 온 친구 요청을 수락한다.", security = {@SecurityRequirement(name = "user_token")}, tags = {"friend"} ) @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "ok" - ) - }) - ResponseEntity> findFriends( - 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 = {"friend"} - ) - @ApiResponses(value = { + description = "처리 완료" + ), @ApiResponse( - responseCode = "200", - description = "ok" + responseCode = "404", + description = "친구 요청이 존재하지 않는 경우 발생" + ), + @ApiResponse( + responseCode = "500", + description = "외부 API 요청 실패" ) }) - ResponseEntity> findFriendRequests( + ResponseEntity> acceptFriendRequest( Long memberId, - @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) - int size, - - @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) - ZonedDateTime createdAt + @Parameter(in = ParameterIn.PATH, required = true, schema = @Schema()) + Long friendId ); @Operation( @@ -111,7 +88,6 @@ ResponseEntity> denyFriendRequest( Long friendId ); - @Operation( summary = "소셜 친구 요청", description = "사용자에게 친구 요청을 보낸다. 해당 사용자에게 알림이 발송된다.", @@ -156,68 +132,4 @@ ResponseEntity> requestFriends( SendFriendRequest request ); - - @Operation( - summary = "소셜 친구 요청 수락", - description = "상대방으로부터 온 친구 요청을 수락한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"friend"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "처리 완료" - ), - @ApiResponse( - responseCode = "404", - description = "친구 요청이 존재하지 않는 경우 발생" - ), - @ApiResponse( - responseCode = "500", - description = "외부 API 요청 실패" - ) - }) - ResponseEntity> acceptFriendRequest( - Long memberId, - - @Parameter(in = ParameterIn.PATH, required = true, schema = @Schema()) - Long friendId - ); - - - @Operation( - summary = "전화번호를 바탕으로 앱 사용자 조회", - description = "전화번호 목록을 받아 ARchive 사용자를 반환한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"friend"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity> searchMembersByPhones( - Long memberId, - SearchFriendsRequest request - ); - - @Operation( - summary = "찬구 검색", - description = "친구의 tag로 친구 검색을 한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"friend"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity> searchFriendByTag( - Long memberId, - - @Parameter(in = ParameterIn.QUERY, description = "친구 태그", required = true) - String tag - ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java new file mode 100644 index 000000000..cf33ef264 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java @@ -0,0 +1,100 @@ +package site.timecapsulearchive.core.domain.friend.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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; +import site.timecapsulearchive.core.domain.friend.service.command.FriendCommandService; +import site.timecapsulearchive.core.global.common.response.ApiSpec; +import site.timecapsulearchive.core.global.common.response.SuccessCode; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/friends") +public class FriendCommandApiController implements FriendCommandApi { + + private final FriendCommandService friendCommandService; + + @DeleteMapping(value = "/{friend_id}") + @Override + public ResponseEntity> deleteFriend( + @AuthenticationPrincipal final Long memberId, + @PathVariable("friend_id") final Long friendId + ) { + friendCommandService.deleteFriend(memberId, friendId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); + } + + @DeleteMapping("/{friend_id}/deny-request") + @Override + public ResponseEntity> denyFriendRequest( + @AuthenticationPrincipal final Long memberId, + @PathVariable("friend_id") final Long friendId) { + + friendCommandService.denyRequestFriend(memberId, friendId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); + } + + @PostMapping(value = "/{friend_id}/request") + @Override + public ResponseEntity> requestFriend( + @AuthenticationPrincipal final Long memberId, + @PathVariable("friend_id") final Long friendId) { + + friendCommandService.requestFriend(memberId, friendId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); + } + + @PostMapping(value = "/requests") + @Override + public ResponseEntity> requestFriends( + @AuthenticationPrincipal final Long memberId, + @RequestBody SendFriendRequest request + ) { + friendCommandService.requestFriends(memberId, request.friendIds()); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.ACCEPTED + ) + ); + } + + @PostMapping(value = "/{friend_id}/accept-request") + @Override + public ResponseEntity> acceptFriendRequest( + @AuthenticationPrincipal final Long memberId, + @PathVariable("friend_id") final Long friendId + ) { + friendCommandService.acceptFriend(memberId, friendId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); + } + + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java new file mode 100644 index 000000000..4440fe8d5 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java @@ -0,0 +1,100 @@ +package site.timecapsulearchive.core.domain.friend.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.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.friend.data.request.SearchFriendsRequest; +import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; +import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; +import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; +import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; +import site.timecapsulearchive.core.global.common.response.ApiSpec; + +public interface FriendQueryApi { + + @Operation( + summary = "소셜 친구 목록 조회", + description = "사용자의 소셜 친구 목록을 보여준다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"friend"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findFriends( + 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 = {"friend"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findFriendRequests( + Long memberId, + + @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) + int size, + + @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) + ZonedDateTime createdAt + ); + + @Operation( + summary = "전화번호를 바탕으로 앱 사용자 조회", + description = "전화번호 목록을 받아 ARchive 사용자를 반환한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"friend"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> searchMembersByPhones( + Long memberId, + SearchFriendsRequest request + ); + + @Operation( + summary = "찬구 검색", + description = "친구의 tag로 친구 검색을 한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"friend"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> searchFriendByTag( + Long memberId, + + @Parameter(in = ParameterIn.QUERY, description = "친구 태그", required = true) + String tag + ); + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java similarity index 57% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java index 3eb3fc44c..dd27955a3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/FriendApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.api; +package site.timecapsulearchive.core.domain.friend.api.query; import jakarta.validation.Valid; import java.time.ZonedDateTime; @@ -7,9 +7,7 @@ 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; @@ -18,25 +16,22 @@ import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; -import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; -import site.timecapsulearchive.core.domain.friend.facade.FriendFacade; -import site.timecapsulearchive.core.domain.friend.service.FriendService; +import site.timecapsulearchive.core.domain.friend.service.query.FriendQueryService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; import site.timecapsulearchive.core.global.security.encryption.HashEncryptionManager; @RestController -@RequestMapping("/friends") @RequiredArgsConstructor -public class FriendApiController implements FriendApi { +@RequestMapping("/friends") +public class FriendQueryApiController implements FriendQueryApi { - private final FriendService friendService; - private final FriendFacade friendFacade; + private final FriendQueryService friendQueryService; private final HashEncryptionManager hashEncryptionManager; @GetMapping @@ -46,7 +41,7 @@ public ResponseEntity> findFriends( @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - Slice friendsSlice = friendService.findFriendsSlice(memberId, size, + Slice friendsSlice = friendQueryService.findFriendsSlice(memberId, size, createdAt); return ResponseEntity.ok( @@ -67,7 +62,7 @@ public ResponseEntity> findFriendRequests( @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - Slice friendRequestsSlice = friendService.findFriendRequestsSlice( + Slice friendRequestsSlice = friendQueryService.findFriendRequestsSlice( memberId, size, createdAt); return ResponseEntity.ok( @@ -79,81 +74,6 @@ public ResponseEntity> findFriendRequests( ); } - @DeleteMapping(value = "/{friend_id}") - @Override - public ResponseEntity> deleteFriend( - @AuthenticationPrincipal final Long memberId, - @PathVariable("friend_id") final Long friendId - ) { - friendService.deleteFriend(memberId, friendId); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.SUCCESS - ) - ); - } - - @DeleteMapping("{friend_id}/deny-request") - @Override - public ResponseEntity> denyFriendRequest( - @AuthenticationPrincipal final Long memberId, - @PathVariable("friend_id") final Long friendId) { - - friendService.denyRequestFriend(memberId, friendId); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.SUCCESS - ) - ); - } - - @PostMapping(value = "/{friend_id}/request") - @Override - public ResponseEntity> requestFriend( - @AuthenticationPrincipal final Long memberId, - @PathVariable("friend_id") final Long friendId) { - - friendService.requestFriend(memberId, friendId); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.ACCEPTED - ) - ); - } - - @PostMapping(value = "/requests") - @Override - public ResponseEntity> requestFriends( - @AuthenticationPrincipal final Long memberId, - @RequestBody SendFriendRequest request - ) { - friendFacade.requestFriends(memberId, request.friendIds()); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.ACCEPTED - ) - ); - } - - @PostMapping(value = "/{friend_id}/accept-request") - @Override - public ResponseEntity> acceptFriendRequest( - @AuthenticationPrincipal final Long memberId, - @PathVariable("friend_id") final Long friendId - ) { - friendService.acceptFriend(memberId, friendId); - - return ResponseEntity.ok( - ApiSpec.empty( - SuccessCode.SUCCESS - ) - ); - } - @PostMapping( value = "/search/phone", produces = {"application/json"}, @@ -167,7 +87,7 @@ public ResponseEntity> searchMembersByPhones( final List phoneEncryption = request.toPhoneEncryption( hashEncryptionManager::encrypt); - final List dtos = friendService.findFriendsByPhone( + final List dtos = friendQueryService.findFriendsByPhone( memberId, phoneEncryption); return ResponseEntity.ok( @@ -187,7 +107,7 @@ public ResponseEntity> searchFriendByTag return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - friendService.searchFriend(memberId, tag) + friendQueryService.searchFriend(memberId, tag) ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/facade/FriendFacade.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/facade/FriendFacade.java deleted file mode 100644 index 5e3bb20d8..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/facade/FriendFacade.java +++ /dev/null @@ -1,43 +0,0 @@ -package site.timecapsulearchive.core.domain.friend.facade; - -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionTemplate; -import site.timecapsulearchive.core.domain.friend.service.FriendService; -import site.timecapsulearchive.core.domain.member.entity.Member; -import site.timecapsulearchive.core.domain.member.service.MemberService; -import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; - -@Component -@RequiredArgsConstructor -public class FriendFacade { - - private final MemberService memberService; - private final FriendService friendService; - private final TransactionTemplate transactionTemplate; - private final SocialNotificationManager socialNotificationManager; - - public void requestFriends(Long memberId, List friendIds) { - final Member[] owner = new Member[1]; - final List[] foundFriendIds = new List[1]; - - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - owner[0] = memberService.findMemberById(memberId); - foundFriendIds[0] = memberService.findMemberIdsByIds(friendIds); - - friendService.bulkSaveFriendInvites(owner[0].getId(), foundFriendIds[0]); - } - }); - - socialNotificationManager.sendFriendRequestMessages( - owner[0].getNickname(), - owner[0].getProfileUrl(), - foundFriendIds[0] - ); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java new file mode 100644 index 000000000..d8887fb0d --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java @@ -0,0 +1,8 @@ +package site.timecapsulearchive.core.domain.friend.repository.friend_invite; + +import java.util.List; + +public interface FriendInviteQueryRepository { + + void bulkSave(final Long ownerId, final List friendIds); +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java similarity index 86% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java index 3e6859eea..136441e2d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.repository; +package site.timecapsulearchive.core.domain.friend.repository.friend_invite; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -13,11 +13,15 @@ @Repository @RequiredArgsConstructor -public class FriendInviteQueryRepository { +public class FriendInviteQueryRepositoryImpl implements FriendInviteQueryRepository { private final JdbcTemplate jdbcTemplate; public void bulkSave(final Long ownerId, final List friendIds) { + if (friendIds.isEmpty()) { + return; + } + jdbcTemplate.batchUpdate( """ INSERT INTO friend_invite ( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java similarity index 89% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java index df8643a77..944e7bedf 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.repository; +package site.timecapsulearchive.core.domain.friend.repository.friend_invite; import java.util.List; import java.util.Optional; @@ -7,7 +7,8 @@ import org.springframework.data.repository.query.Param; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; -public interface FriendInviteRepository extends Repository { +public interface FriendInviteRepository extends Repository, + FriendInviteQueryRepository { void save(FriendInvite friendInvite); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java new file mode 100644 index 000000000..fa0f1df47 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java @@ -0,0 +1,37 @@ +package site.timecapsulearchive.core.domain.friend.repository.member_friend; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Slice; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; +import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; +import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; + + +public interface MemberFriendQueryRepository { + + Slice findFriendsSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ); + + Slice findFriendRequestsSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ); + + List findFriendsByPhone( + final Long memberId, + final List hashes + ); + + Optional findFriendsByTag( + final Long memberId, + final String tag + ); + + List findFriendIdsByOwnerId(final Long memberId); +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java similarity index 97% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 412deff3f..70fcbcd9e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.repository; +package site.timecapsulearchive.core.domain.friend.repository.member_friend; import static site.timecapsulearchive.core.domain.friend.entity.QFriendInvite.friendInvite; import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; @@ -22,7 +22,7 @@ @Repository @RequiredArgsConstructor -public class MemberFriendQueryRepository { +public class MemberFriendQueryRepositoryImpl implements MemberFriendQueryRepository { private final JPAQueryFactory jpaQueryFactory; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendRepository.java similarity index 85% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendRepository.java index 9f5dac8e3..de37d3dcd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.repository; +package site.timecapsulearchive.core.domain.friend.repository.member_friend; import java.util.List; import org.springframework.data.jpa.repository.Query; @@ -6,7 +6,8 @@ import org.springframework.data.repository.query.Param; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; -public interface MemberFriendRepository extends Repository { +public interface MemberFriendRepository extends Repository, + MemberFriendQueryRepository { @Query("SELECT mf FROM MemberFriend mf " + "WHERE (mf.owner.id = :memberId AND mf.friend.id = :friendId) " diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java similarity index 64% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java index 68fb8cf6f..00ada5929 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/FriendService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java @@ -1,47 +1,57 @@ -package site.timecapsulearchive.core.domain.friend.service; +package site.timecapsulearchive.core.domain.friend.service.command; -import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; 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.friend.data.dto.FriendSummaryDto; -import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; -import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; -import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; import site.timecapsulearchive.core.domain.friend.exception.FriendDuplicateIdException; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; -import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendTwoWayInviteException; -import site.timecapsulearchive.core.domain.friend.repository.FriendInviteQueryRepository; -import site.timecapsulearchive.core.domain.friend.repository.FriendInviteRepository; -import site.timecapsulearchive.core.domain.friend.repository.MemberFriendQueryRepository; -import site.timecapsulearchive.core.domain.friend.repository.MemberFriendRepository; +import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteRepository; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; 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.common.wrapper.ByteArrayWrapper; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Service @RequiredArgsConstructor -public class FriendService { +public class FriendCommandService { private final MemberFriendRepository memberFriendRepository; - private final MemberFriendQueryRepository memberFriendQueryRepository; private final MemberRepository memberRepository; private final FriendInviteRepository friendInviteRepository; - private final FriendInviteQueryRepository friendInviteQueryRepository; private final SocialNotificationManager socialNotificationManager; private final TransactionTemplate transactionTemplate; + public void requestFriends(Long memberId, List friendIds) { + final Member[] owner = new Member[1]; + final List[] foundFriendIds = new List[1]; + + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + owner[0] = memberRepository.findMemberById(memberId) + .orElseThrow(MemberNotFoundException::new); + foundFriendIds[0] = memberRepository.findMemberIdsByIds(friendIds); + + friendInviteRepository.bulkSave(owner[0].getId(), foundFriendIds[0]); + } + }); + + socialNotificationManager.sendFriendRequestMessages( + owner[0].getNickname(), + owner[0].getProfileUrl(), + foundFriendIds[0] + ); + } + public void requestFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); validateTwoWayInvite(memberId, friendId); @@ -132,50 +142,4 @@ public void deleteFriend(final Long memberId, final Long friendId) { memberFriends.forEach(memberFriendRepository::delete); } - @Transactional(readOnly = true) - public Slice findFriendsSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ) { - return memberFriendQueryRepository.findFriendsSlice(memberId, size, createdAt); - } - - @Transactional(readOnly = true) - public Slice findFriendRequestsSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ) { - return memberFriendQueryRepository.findFriendRequestsSlice(memberId, size, createdAt); - } - - @Transactional(readOnly = true) - public List findFriendsByPhone( - final Long memberId, - final List phoneEncryption - ) { - final List hashes = phoneEncryption.stream().map(ByteArrayWrapper::getData) - .toList(); - - return memberFriendQueryRepository.findFriendsByPhone(memberId, hashes); - } - - - @Transactional(readOnly = true) - public SearchTagFriendSummaryResponse searchFriend(final Long memberId, final String tag) { - final SearchFriendSummaryDtoByTag friendSummaryDto = memberFriendQueryRepository - .findFriendsByTag(memberId, tag).orElseThrow(FriendNotFoundException::new); - - return friendSummaryDto.toResponse(); - } - - @Transactional - public void bulkSaveFriendInvites(Long ownerId, List friendIds) { - if (friendIds.isEmpty()) { - return; - } - - friendInviteQueryRepository.bulkSave(ownerId, friendIds); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java new file mode 100644 index 000000000..862f81d84 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -0,0 +1,57 @@ +package site.timecapsulearchive.core.domain.friend.service.query; + +import java.time.ZonedDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; +import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; +import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; +import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; +import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; +import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class FriendQueryService { + + private final MemberFriendRepository memberFriendRepository; + + public Slice findFriendsSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + return memberFriendRepository.findFriendsSlice(memberId, size, createdAt); + } + + public Slice findFriendRequestsSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + return memberFriendRepository.findFriendRequestsSlice(memberId, size, createdAt); + } + + public List findFriendsByPhone( + final Long memberId, + final List phoneEncryption + ) { + final List hashes = phoneEncryption.stream().map(ByteArrayWrapper::getData) + .toList(); + + return memberFriendRepository.findFriendsByPhone(memberId, hashes); + } + + public SearchTagFriendSummaryResponse searchFriend(final Long memberId, final String tag) { + final SearchFriendSummaryDtoByTag friendSummaryDto = memberFriendRepository + .findFriendsByTag(memberId, tag).orElseThrow(FriendNotFoundException::new); + + return friendSummaryDto.toResponse(); + } + +} 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 index 70cf97f74..4e06b0a5d 100644 --- 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 @@ -9,9 +9,6 @@ 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; @@ -94,11 +91,11 @@ ResponseEntity> createGroup( @ApiResponse( responseCode = "200", description = "처리 완료" - ),@ApiResponse( - responseCode = "400", - description = "잘못된 타입이나 값을 입력하는 경우 발생한다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), + ), @ApiResponse( + responseCode = "400", + description = "잘못된 타입이나 값을 입력하는 경우 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), @ApiResponse( responseCode = "403", description = "그룹장이 아니여서 그룹 수정에 대한 권한이 없는 경우 발생한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index 9a29abc53..ff74a6692 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -3,8 +3,8 @@ 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.member_group.entity.QMemberGroup.memberGroup; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; +import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index 113ba2a7c..7b274330f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -14,14 +14,14 @@ import site.timecapsulearchive.core.domain.group.exception.GroupDeleteFailException; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; +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.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.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.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; import site.timecapsulearchive.core.infra.s3.manager.S3ObjectManager; 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 c8da6e86d..31cdfff02 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,8 +19,8 @@ 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.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.history.entity.History; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; 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 d7daa0bc8..469bb89a5 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,174 +1,47 @@ package site.timecapsulearchive.core.domain.member.repository; -import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; -import static site.timecapsulearchive.core.domain.member_group.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; - -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.NumberExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import org.springframework.stereotype.Repository; import site.timecapsulearchive.core.domain.member.data.dto.EmailVerifiedCheckDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberDetailDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; import site.timecapsulearchive.core.domain.member.entity.SocialType; -@Repository -@RequiredArgsConstructor -public class MemberQueryRepository { +public interface MemberQueryRepository { - private final JPAQueryFactory query; - public Boolean findIsVerifiedByAuthIdAndSocialType( + Boolean findIsVerifiedByAuthIdAndSocialType( final String authId, final SocialType socialType - ) { - return query.select(member.isVerified) - .from(member) - .where(member.authId.eq(authId).and(member.socialType.eq(socialType))) - .fetchOne(); - } + ); - public Optional findVerifiedCheckDtoByAuthIdAndSocialType( + Optional findVerifiedCheckDtoByAuthIdAndSocialType( final String authId, final SocialType socialType - ) { - return Optional.ofNullable( - query - .select( - Projections.constructor( - VerifiedCheckDto.class, - member.id, - member.isVerified - ) - ) - .from(member) - .where(member.authId.eq(authId).and(member.socialType.eq(socialType))) - .fetchOne() - ); - } - - public Optional findMemberDetailResponseDtoById(final Long memberId) { - return Optional.ofNullable( - query - .select( - Projections.constructor( - MemberDetailDto.class, - member.nickname, - member.profileUrl, - member.tag, - countDistinct(memberFriend.id), - countDistinct(memberGroup.id) - ) - ) - .from(member) - .leftJoin(memberFriend).on(member.id.eq(memberFriend.owner.id)) - .leftJoin(memberGroup).on(member.id.eq(memberGroup.member.id)) - .where(member.id.eq(memberId)) - .groupBy(member.id) - .fetchOne() - ); - } + ); - private NumberExpression countDistinct(final NumberExpression expression) { - return Expressions.numberTemplate(Long.class, "COUNT(DISTINCT {0})", expression); - } + Optional findMemberDetailResponseDtoById(final Long memberId); - public Slice findNotificationSliceByMemberId( + Slice findNotificationSliceByMemberId( final Long memberId, final int size, final ZonedDateTime createdAt - ) { - final List notifications = findMemberNotificationDtos( - memberId, size, createdAt); - - final boolean hasNext = notifications.size() > size; - if (hasNext) { - notifications.remove(size); - } + ); - return new SliceImpl<>(notifications, Pageable.ofSize(size), hasNext); - } - - private List findMemberNotificationDtos( + List findMemberNotificationDtos( final Long memberId, final int size, final ZonedDateTime createdAt - ) { - return query - .select( - Projections.constructor( - MemberNotificationDto.class, - notification.title, - notification.text, - notification.createdAt, - notification.imageUrl, - notificationCategory.categoryName, - notification.status - ) - ) - .from(notification) - .join(notification.notificationCategory, notificationCategory) - .where(notification.createdAt.lt(createdAt).and(notification.member.id.eq(memberId))) - .orderBy(notification.id.desc()) - .limit(size + 1) - .fetch(); - } - - public Optional findEmailVerifiedCheckDtoByEmail( - final String email - ) { - return Optional.ofNullable( - query - .select( - Projections.constructor( - EmailVerifiedCheckDto.class, - member.id, - member.isVerified, - member.email, - member.password - ) - ) - .from(member) - .where(member.email.eq(email)) - .fetchOne() - ); - } + ); - public Boolean checkEmailDuplication(final String email) { - final Integer count = query.selectOne() - .from(member) - .where(member.email.eq(email)) - .fetchFirst(); + Optional findEmailVerifiedCheckDtoByEmail(final String email); - return count != null; - } + Boolean checkEmailDuplication(final String email); - public Optional findIsAlarmByMemberId(final Long memberId) { - return Optional.ofNullable( - query.select(member.notificationEnabled) - .from(member) - .where(member.id.eq(memberId)) - .fetchOne() - ); - } + Optional findIsAlarmByMemberId(final Long memberId); - public List findMemberIdsByIds(List ids) { - return query - .select(member.id) - .from(member) - .where(member.id.in(ids)) - .fetch(); - } + List findMemberIdsByIds(List ids); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java new file mode 100644 index 000000000..27453148a --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java @@ -0,0 +1,174 @@ +package site.timecapsulearchive.core.domain.member.repository; + +import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; +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; +import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; + +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.member.data.dto.EmailVerifiedCheckDto; +import site.timecapsulearchive.core.domain.member.data.dto.MemberDetailDto; +import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; +import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; +import site.timecapsulearchive.core.domain.member.entity.SocialType; + +@Repository +@RequiredArgsConstructor +public class MemberQueryRepositoryImpl implements MemberQueryRepository { + + private final JPAQueryFactory query; + + public Boolean findIsVerifiedByAuthIdAndSocialType( + final String authId, + final SocialType socialType + ) { + return query.select(member.isVerified) + .from(member) + .where(member.authId.eq(authId).and(member.socialType.eq(socialType))) + .fetchOne(); + } + + public Optional findVerifiedCheckDtoByAuthIdAndSocialType( + final String authId, + final SocialType socialType + ) { + return Optional.ofNullable( + query + .select( + Projections.constructor( + VerifiedCheckDto.class, + member.id, + member.isVerified + ) + ) + .from(member) + .where(member.authId.eq(authId).and(member.socialType.eq(socialType))) + .fetchOne() + ); + } + + public Optional findMemberDetailResponseDtoById(final Long memberId) { + return Optional.ofNullable( + query + .select( + Projections.constructor( + MemberDetailDto.class, + member.nickname, + member.profileUrl, + member.tag, + countDistinct(memberFriend.id), + countDistinct(memberGroup.id) + ) + ) + .from(member) + .leftJoin(memberFriend).on(member.id.eq(memberFriend.owner.id)) + .leftJoin(memberGroup).on(member.id.eq(memberGroup.member.id)) + .where(member.id.eq(memberId)) + .groupBy(member.id) + .fetchOne() + ); + } + + private NumberExpression countDistinct(final NumberExpression expression) { + return Expressions.numberTemplate(Long.class, "COUNT(DISTINCT {0})", expression); + } + + public Slice findNotificationSliceByMemberId( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + final List notifications = findMemberNotificationDtos( + memberId, size, createdAt); + + final boolean hasNext = notifications.size() > size; + if (hasNext) { + notifications.remove(size); + } + + return new SliceImpl<>(notifications, Pageable.ofSize(size), hasNext); + } + + public List findMemberNotificationDtos( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + return query + .select( + Projections.constructor( + MemberNotificationDto.class, + notification.title, + notification.text, + notification.createdAt, + notification.imageUrl, + notificationCategory.categoryName, + notification.status + ) + ) + .from(notification) + .join(notification.notificationCategory, notificationCategory) + .where(notification.createdAt.lt(createdAt).and(notification.member.id.eq(memberId))) + .orderBy(notification.id.desc()) + .limit(size + 1) + .fetch(); + } + + public Optional findEmailVerifiedCheckDtoByEmail( + final String email + ) { + return Optional.ofNullable( + query + .select( + Projections.constructor( + EmailVerifiedCheckDto.class, + member.id, + member.isVerified, + member.email, + member.password + ) + ) + .from(member) + .where(member.email.eq(email)) + .fetchOne() + ); + } + + public Boolean checkEmailDuplication(final String email) { + final Integer count = query.selectOne() + .from(member) + .where(member.email.eq(email)) + .fetchFirst(); + + return count != null; + } + + public Optional findIsAlarmByMemberId(final Long memberId) { + return Optional.ofNullable( + query.select(member.notificationEnabled) + .from(member) + .where(member.id.eq(memberId)) + .fetchOne() + ); + } + + public List findMemberIdsByIds(List ids) { + return query + .select(member.id) + .from(member) + .where(member.id.in(ids)) + .fetch(); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java index 9e8f9b31d..02c68c260 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java @@ -8,7 +8,7 @@ import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.entity.SocialType; -public interface MemberRepository extends Repository { +public interface MemberRepository extends Repository, MemberQueryRepository { Optional findMemberByAuthIdAndSocialType(String authId, SocialType socialType); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 494c3f0f1..7799528d0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -25,7 +25,6 @@ import site.timecapsulearchive.core.domain.member.exception.CredentialsNotMatchedException; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.exception.NotVerifiedMemberException; -import site.timecapsulearchive.core.domain.member.repository.MemberQueryRepository; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; @@ -37,7 +36,6 @@ public class MemberService { private final MemberRepository memberRepository; private final MemberTemporaryRepository memberTemporaryRepository; - private final MemberQueryRepository memberQueryRepository; private final PasswordEncoder passwordEncoder; private final MemberMapper memberMapper; @@ -63,7 +61,7 @@ public MemberStatusResponse checkStatus( final SocialType socialType ) { - final Boolean isVerified = memberQueryRepository.findIsVerifiedByAuthIdAndSocialType( + final Boolean isVerified = memberRepository.findIsVerifiedByAuthIdAndSocialType( authId, socialType ); @@ -87,7 +85,7 @@ public Long findVerifiedMemberIdByAuthIdAndSocialType( final String authId, final SocialType socialType ) throws NotVerifiedMemberException { - final VerifiedCheckDto dto = memberQueryRepository.findVerifiedCheckDtoByAuthIdAndSocialType( + final VerifiedCheckDto dto = memberRepository.findVerifiedCheckDtoByAuthIdAndSocialType( authId, socialType) .orElseThrow(MemberNotFoundException::new); @@ -109,8 +107,8 @@ public Long findVerifiedMemberIdByAuthIdAndSocialType( public Long findNotVerifiedMemberIdByAuthIdAndSocialType( final String authId, final SocialType socialType - ) throws AlreadyVerifiedException { - final VerifiedCheckDto dto = memberQueryRepository.findVerifiedCheckDtoByAuthIdAndSocialType( + ) { + final VerifiedCheckDto dto = memberRepository.findVerifiedCheckDtoByAuthIdAndSocialType( authId, socialType) .orElseThrow(MemberNotFoundException::new); @@ -122,7 +120,7 @@ public Long findNotVerifiedMemberIdByAuthIdAndSocialType( } public MemberDetailDto findMemberDetailById(final Long memberId) { - return memberQueryRepository.findMemberDetailResponseDtoById(memberId) + return memberRepository.findMemberDetailResponseDtoById(memberId) .orElseThrow(MemberNotFoundException::new); } @@ -153,7 +151,7 @@ public MemberNotificationSliceResponse findNotificationSliceByMemberId( final int size, final ZonedDateTime createdAt ) { - final Slice notifications = memberQueryRepository.findNotificationSliceByMemberId( + final Slice notifications = memberRepository.findNotificationSliceByMemberId( memberId, size, createdAt); return memberMapper.notificationSliceToResponse( @@ -173,7 +171,7 @@ public Long createMemberWithEmailAndPassword(final String email, final String pa } public Long findVerifiedMemberIdByEmailAndPassword(final String email, final String password) { - final EmailVerifiedCheckDto dto = memberQueryRepository.findEmailVerifiedCheckDtoByEmail( + final EmailVerifiedCheckDto dto = memberRepository.findEmailVerifiedCheckDtoByEmail( email) .orElseThrow(MemberNotFoundException::new); @@ -200,13 +198,13 @@ private boolean isNotMatched( } public CheckEmailDuplicationResponse checkEmailDuplication(final String email) { - Boolean isDuplicated = memberQueryRepository.checkEmailDuplication(email); + Boolean isDuplicated = memberRepository.checkEmailDuplication(email); return new CheckEmailDuplicationResponse(isDuplicated); } public MemberNotificationStatusResponse checkNotificationStatus(final Long memberId) { - final Boolean isAlarm = memberQueryRepository.findIsAlarmByMemberId(memberId) + final Boolean isAlarm = memberRepository.findIsAlarmByMemberId(memberId) .orElseThrow(MemberNotFoundException::new); return new MemberNotificationStatusResponse(isAlarm); @@ -218,6 +216,6 @@ public Member findMemberById(final Long memberId) { } public List findMemberIdsByIds(List ids) { - return memberQueryRepository.findMemberIdsByIds(ids); + return memberRepository.findMemberIdsByIds(ids); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java index 58c5566f2..26e38f030 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java @@ -53,6 +53,7 @@ ResponseEntity> quitGroup( @Parameter(in = ParameterIn.PATH, description = "탈퇴할 그룹 아이디", required = true) Long groupId ); + @Operation( summary = "그룹 요청", description = "그룹장인 경우 친구에게 그룹 가입 요청을 할 수 있다.", @@ -126,7 +127,7 @@ ResponseEntity> rejectGroupInvitation( Long groupOwnerId ); - @Operation( + @Operation( summary = "그룹원 추방", description = "요청한 사용자가 그룹장인 경우 특정 그룹원을 그룹에서 추방한다.", security = {@SecurityRequirement(name = "user_token")}, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 243066464..6d09460dc 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -1,8 +1,8 @@ package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; -import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; +import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index fe238958a..4ae38d8a1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -10,19 +10,19 @@ import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; +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.domain.member_group.data.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; -import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.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.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @@ -122,7 +122,7 @@ public void quitGroup(final Long memberId, final Long groupId) { memberGroupRepository.delete(groupMember); } - /** + /** * 그룹의 회원을 그룹에서 삭제한다. *
주의 - 그룹원 삭제 시 아래 조건에 해당하면 예외가 발생한다. *
1. 자신을 삭제하려한 경우 diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java index f2cfda28c..ca77597cb 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java @@ -8,7 +8,6 @@ import site.timecapsulearchive.core.infra.s3.config.S3Config; import site.timecapsulearchive.core.infra.s3.config.S3Properties; import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; -import site.timecapsulearchive.core.infra.s3.manager.S3UrlGenerator; /** * 유닛 테스트를 위한 의존성 클래스 집합체 diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java index b691ddd19..386ed7a1e 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java @@ -56,13 +56,14 @@ private static Address getTestAddress() { /** * 그룹 캡슐의 테스트 픽스처들을 생성한다 * - * @param size 테스트 픽스처를 만들 캡슐의 개수 + * @param size 테스트 픽스처를 만들 캡슐의 개수 * @param member 그룹 캡슐을 생성할 멤버 * @param capsuleSkin 캡슐 스킨 * @param group 그룹 * @return 그룹 캡슐 테스트 픽스처 */ - public static List groupCapsules(int size, Member member, CapsuleSkin capsuleSkin, Group group) { + public static List groupCapsules(int size, Member member, CapsuleSkin capsuleSkin, + Group group) { return IntStream .range(0, size) .mapToObj(i -> groupCapsule(member, capsuleSkin, group)) @@ -72,9 +73,9 @@ public static List groupCapsules(int size, Member member, CapsuleSkin c /** * 그룹 캡슐의 테스트 픽스처를 생성한다 * - * @param member 그룹 캡슐을 생성할 멤버 + * @param member 그룹 캡슐을 생성할 멤버 * @param capsuleSkin 캡슐 스킨 - * @param group 그룹 + * @param group 그룹 * @return 그룹 캡슐 테스트 픽스처 */ public static Capsule groupCapsule(Member member, CapsuleSkin capsuleSkin, Group group) { 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 137523db4..39f20c15d 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 @@ -6,8 +6,8 @@ import java.util.List; import java.util.Optional; import site.timecapsulearchive.core.domain.group.entity.Group; -import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; public class MemberGroupFixture { @@ -15,7 +15,7 @@ public class MemberGroupFixture { * 테스트 픽스처로 그룹장으로 그룹 멤버를 생성한다 * * @param member 그룹장이 될 멤버 - * @param group 대상 그룹 + * @param group 대상 그룹 * @return 그룹 멤버 테스트 픽스처 */ public static MemberGroup groupOwner(Member member, Group group) { @@ -24,8 +24,9 @@ public static MemberGroup groupOwner(Member member, Group group) { /** * 테스트 픽스처 - 사용자, 그룹, 그룹장 여부를 받아 그룹원을 만들어준다. - * @param member 사용자 - * @param group 그룹 + * + * @param member 사용자 + * @param group 그룹 * @param isOwner 그룹장 여부 * @return MemberGroup 테스트 픽스처 */ @@ -54,8 +55,9 @@ private static void setFieldValue(Object instance, String fieldName, Object valu /** * 테스트 픽스처 - 그룹원들과 그룹을 주면 그룹원들 목록을 만들어준다(그룹장 X) + * * @param members 그룹원들 목록 - * @param group 그룹 + * @param group 그룹 * @return {@code List} 테스트 픽스처들 */ public static List memberGroups(List members, Group group) { @@ -66,6 +68,7 @@ public static List memberGroups(List members, Group group) /** * 임의로 그룹원들과 그룹장만 만든다. + * * @return 그룹원들과 그룹장 {@code List} */ public static List memberGroupsWithOwner() { @@ -85,6 +88,7 @@ public static List memberGroupsWithOwner() { /** * 임의로 그룹원들만 만든다. + * * @return 그룹원들 {@code List} */ public static List memberGroupsWithoutOwner() { @@ -99,6 +103,7 @@ public static List memberGroupsWithoutOwner() { /** * 테스트픽스처 - 임의로 그룹원을 만든다. + * * @return 그룹원인 {@code MemberGroup} */ public static Optional notOwner() { @@ -113,6 +118,7 @@ public static Optional notOwner() { /** * 테스트픽스처 - 임의로 그룹장을 만든다. + * * @return 그룹장인 {@code MemberGroup} */ public static Optional owner() { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/CapsuleDtoFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/CapsuleDtoFixture.java index ac624f9c4..be4754179 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/CapsuleDtoFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/CapsuleDtoFixture.java @@ -11,7 +11,7 @@ public class CapsuleDtoFixture { - private static ZonedDateTime now = ZonedDateTime.now(); + private static final ZonedDateTime now = ZonedDateTime.now(); public static Optional getCapsuleDetailDto(Long capsuleId, Boolean isOpened, ZonedDateTime dueDate) { @@ -24,8 +24,9 @@ public static Optional getCapsuleDetailDto(Long capsuleId, Boo public static Optional getGroupCapsuleDetailDto(Long capsuleId, boolean isOpened, ZonedDateTime now, int count) { - return Optional.of(new GroupCapsuleDetailDto(getCapsuleDetailDto(capsuleId, isOpened, now).get(), - getGroupMemberSummaryDtos(count))); + return Optional.of( + new GroupCapsuleDetailDto(getCapsuleDetailDto(capsuleId, isOpened, now).get(), + getGroupMemberSummaryDtos(count))); } private static List getGroupMemberSummaryDtos(int count) { 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 bc72de543..47f1036fa 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,8 +33,8 @@ 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.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; @TestConstructor(autowireMode = AutowireMode.ALL) class GroupCapsuleQueryRepositoryTest extends RepositoryTest { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/repository/PublicCapsuleQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/repository/PublicCapsuleQueryRepositoryTest.java index f31b91a8a..3181f65e8 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/repository/PublicCapsuleQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/repository/PublicCapsuleQueryRepositoryTest.java @@ -25,7 +25,6 @@ import site.timecapsulearchive.core.domain.capsule.generic_capsule.data.dto.CapsuleSummaryDto; import site.timecapsulearchive.core.domain.capsule.public_capsule.data.dto.MyPublicCapsuleDto; import site.timecapsulearchive.core.domain.capsule.public_capsule.data.dto.PublicCapsuleDetailDto; -import site.timecapsulearchive.core.domain.capsule.public_capsule.repository.PublicCapsuleQueryRepository; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; import site.timecapsulearchive.core.domain.member.entity.Member; diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/service/PublicCapsuleServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/service/PublicCapsuleServiceTest.java index 009771110..d8825c179 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/service/PublicCapsuleServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/public_capsule/service/PublicCapsuleServiceTest.java @@ -11,7 +11,6 @@ import site.timecapsulearchive.core.common.fixture.dto.CapsuleDtoFixture; import site.timecapsulearchive.core.domain.capsule.generic_capsule.data.dto.CapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.public_capsule.repository.PublicCapsuleQueryRepository; -import site.timecapsulearchive.core.domain.capsule.public_capsule.service.PublicCapsuleService; import site.timecapsulearchive.core.global.geography.GeoTransformConfig; import site.timecapsulearchive.core.infra.s3.config.S3Config; diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java index f9dd6ed0b..5c6459400 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java @@ -42,7 +42,8 @@ public GroupCapsuleOpenQueryRepositoryTest( DataSource dataSource ) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate, jpaQueryFactory); + this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate, + jpaQueryFactory); } @Transactional diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java index 3c86b59b8..c4c63562f 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java @@ -14,6 +14,8 @@ import site.timecapsulearchive.core.common.RepositoryTest; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; +import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteQueryRepository; +import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteQueryRepositoryImpl; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) @@ -27,7 +29,7 @@ class FriendInviteQueryRepositoryTest extends RepositoryTest { FriendInviteQueryRepositoryTest(EntityManager entityManager, JdbcTemplate jdbcTemplate) { this.entityManager = entityManager; - this.friendInviteQueryRepository = new FriendInviteQueryRepository(jdbcTemplate); + this.friendInviteQueryRepository = new FriendInviteQueryRepositoryImpl(jdbcTemplate); } @BeforeEach diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index c82512797..7831111fe 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -29,6 +29,8 @@ import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendQueryRepository; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendQueryRepositoryImpl; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) @@ -42,13 +44,12 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private final List hashedNotMemberPhones = new ArrayList<>(); private final List hashedFriendPhones = new ArrayList<>(); private final List hashedNotFriendPhones = new ArrayList<>(); - + private final MemberFriendQueryRepository memberFriendQueryRepository; private Long ownerId; private Long friendId; - private final MemberFriendQueryRepository memberFriendQueryRepository; MemberFriendQueryRepositoryTest(EntityManager entityManager) { - this.memberFriendQueryRepository = new MemberFriendQueryRepository( + this.memberFriendQueryRepository = new MemberFriendQueryRepositoryImpl( new JPAQueryFactory(entityManager)); } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepositoryTest.java index 7b4b9bf6d..2a449daab 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepositoryTest.java @@ -15,6 +15,7 @@ import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFriendFixture; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java similarity index 59% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendServiceTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java index 4a20b1fcb..8eea61d4b 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java @@ -14,45 +14,23 @@ import java.util.Optional; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; -import org.springframework.transaction.support.TransactionTemplate; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.FriendDtoFixture; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; -import site.timecapsulearchive.core.domain.friend.repository.FriendInviteQueryRepository; -import site.timecapsulearchive.core.domain.friend.repository.FriendInviteRepository; -import site.timecapsulearchive.core.domain.friend.repository.MemberFriendQueryRepository; -import site.timecapsulearchive.core.domain.friend.repository.MemberFriendRepository; -import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; +import site.timecapsulearchive.core.domain.friend.service.query.FriendQueryService; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; -import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; -class FriendServiceTest { +class FriendQueryServiceTest { - private final MemberFriendQueryRepository memberFriendQueryRepository = mock( - MemberFriendQueryRepository.class); private final MemberFriendRepository memberFriendRepository = mock( MemberFriendRepository.class); - private final MemberRepository memberRepository = mock(MemberRepository.class); - private final FriendInviteRepository friendInviteRepository = mock( - FriendInviteRepository.class); - private final FriendInviteQueryRepository friendInviteQueryRepository = mock( - FriendInviteQueryRepository.class); - private final SocialNotificationManager notificationManager = mock( - SocialNotificationManager.class); - private final TransactionTemplate transactionTemplate = mock(TransactionTemplate.class); - - private final FriendService friendService = new FriendService( - memberFriendRepository, - memberFriendQueryRepository, - memberRepository, - friendInviteRepository, - friendInviteQueryRepository, - notificationManager, - transactionTemplate - ); + + private final FriendQueryService friendQueryService = new FriendQueryService( + memberFriendRepository); @Test void 사용자는_주소록_기반_핸드폰_번호로_Ahchive_사용자_리스트를_조회_할_수_있다() { @@ -60,11 +38,11 @@ class FriendServiceTest { Long memberId = 1L; List phones = MemberFixture.getPhones(5); - given(memberFriendQueryRepository.findFriendsByPhone(anyLong(), anyList())) + given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) .willReturn(FriendDtoFixture.getFriendSummaryDtos(5)); //when - List dtos = friendService.findFriendsByPhone(memberId, phones); + List dtos = friendQueryService.findFriendsByPhone(memberId, phones); //then assertThat(dtos.size()).isEqualTo(phones.size()); @@ -76,11 +54,11 @@ class FriendServiceTest { //given Long memberId = 1L; List phones = Collections.emptyList(); - given(memberFriendQueryRepository.findFriendsByPhone(anyLong(), anyList())) + given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) .willReturn(Collections.emptyList()); //when - List dtos = friendService.findFriendsByPhone(memberId, + List dtos = friendQueryService.findFriendsByPhone(memberId, phones); //then @@ -95,11 +73,11 @@ class FriendServiceTest { Optional summaryDtoByTag = FriendDtoFixture.getFriendSummaryDtoByTag(); SearchFriendSummaryDtoByTag expectDto = summaryDtoByTag.get(); - given(memberFriendQueryRepository.findFriendsByTag(anyLong(), anyString())) + given(memberFriendRepository.findFriendsByTag(anyLong(), anyString())) .willReturn(summaryDtoByTag); //when - SearchTagFriendSummaryResponse actualResponse = friendService.searchFriend( + SearchTagFriendSummaryResponse actualResponse = friendQueryService.searchFriend( memberId, tag); //then @@ -117,11 +95,11 @@ class FriendServiceTest { Long memberId = 1L; String tag = "testTag"; - given(memberFriendQueryRepository.findFriendsByTag(anyLong(), anyString())) + given(memberFriendRepository.findFriendsByTag(anyLong(), anyString())) .willReturn(Optional.empty()); //when - assertThatThrownBy(() -> friendService.searchFriend(memberId, tag)) + assertThatThrownBy(() -> friendQueryService.searchFriend(memberId, tag)) .isInstanceOf(FriendNotFoundException.class); } } \ No newline at end of file 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 d037249b6..eadfa29a5 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,8 +25,8 @@ 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.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; @TestConstructor(autowireMode = AutowireMode.ALL) class MemberGroupQueryRepositoryTest extends RepositoryTest { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java index c6a32a190..f6b55a63f 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java @@ -29,13 +29,13 @@ import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.group.service.command.GroupCommandService; +import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.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; @@ -238,7 +238,8 @@ private GroupUpdateDto getGroupUpdateDto() { //when //then - assertThatThrownBy(() -> groupCommandService.updateGroup(notGroupOwnerId, groupId, groupUpdateDto)) + assertThatThrownBy( + () -> groupCommandService.updateGroup(notGroupOwnerId, groupId, groupUpdateDto)) .isInstanceOf(NoGroupAuthorityException.class) .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); } @@ -255,7 +256,8 @@ private GroupUpdateDto getGroupUpdateDto() { //when //then - assertThatThrownBy(() -> groupCommandService.updateGroup(groupOwnerId, groupId, groupUpdateDto)) + assertThatThrownBy( + () -> groupCommandService.updateGroup(groupOwnerId, groupId, groupUpdateDto)) .isInstanceOf(GroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java index 8686e9114..9ddf1e92f 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java @@ -39,7 +39,7 @@ class MemberQueryRepositoryTest extends RepositoryTest { private int friendCount; MemberQueryRepositoryTest(EntityManager entityManager) { - this.memberQueryRepository = new MemberQueryRepository( + this.memberQueryRepository = new MemberQueryRepositoryImpl( new JPAQueryFactory(entityManager)); } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java index 34d20f782..dd2095c84 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java @@ -15,22 +15,20 @@ import site.timecapsulearchive.core.domain.member.exception.CredentialsNotMatchedException; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.exception.NotVerifiedMemberException; -import site.timecapsulearchive.core.domain.member.repository.MemberQueryRepository; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; class MemberServiceTest { private final MemberRepository memberRepository = mock(MemberRepository.class); - private final MemberTemporaryRepository memberTemporaryRepository = mock(MemberTemporaryRepository.class); - private final MemberQueryRepository memberQueryRepository = mock(MemberQueryRepository.class); + private final MemberTemporaryRepository memberTemporaryRepository = mock( + MemberTemporaryRepository.class); private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); private final MemberMapper memberMapper = mock(MemberMapper.class); private final MemberService memberService = new MemberService( memberRepository, memberTemporaryRepository, - memberQueryRepository, passwordEncoder, memberMapper ); @@ -40,7 +38,7 @@ class MemberServiceTest { //given String email = "test@google.com"; String password = "test-password"; - given(memberQueryRepository.findEmailVerifiedCheckDtoByEmail(anyString())) + given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) .willReturn(Optional.empty()); //when, then @@ -54,7 +52,7 @@ class MemberServiceTest { //given String email = "test@google.com"; String password = "test-password"; - given(memberQueryRepository.findEmailVerifiedCheckDtoByEmail(anyString())) + given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); //when @@ -82,7 +80,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //given String email = "test@google.com"; String password = "test-password"; - given(memberQueryRepository.findEmailVerifiedCheckDtoByEmail(anyString())) + given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) .willReturn(getVerifiedCheckDto(Boolean.FALSE, email, password)); //when, then @@ -96,7 +94,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //given String email = "test@google.com"; String password = "test-password"; - given(memberQueryRepository.findEmailVerifiedCheckDtoByEmail(anyString())) + given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); //when, then @@ -110,7 +108,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //given String email = "test@google.com"; String password = "test-password"; - given(memberQueryRepository.findEmailVerifiedCheckDtoByEmail(anyString())) + given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); //when, then @@ -124,7 +122,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //given String email = "test@google.com"; String password = "test-password"; - given(memberQueryRepository.findEmailVerifiedCheckDtoByEmail(anyString())) + given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, null)); //when, then @@ -138,7 +136,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //given String email = "test@google.com"; String password = "test-password"; - given(memberQueryRepository.findEmailVerifiedCheckDtoByEmail(anyString())) + given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); //when, then diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 284a7eb5f..6f3d580bf 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -21,17 +21,17 @@ 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.member.entity.Member; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; -import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.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; @@ -258,7 +258,8 @@ public class MemberGroupCommandServiceTest { //when //then assertThatThrownBy( - () -> groupMemberCommandService.kickGroupMember(notExistGroupOwnerId, groupId, groupMemberId)) + () -> groupMemberCommandService.kickGroupMember(notExistGroupOwnerId, groupId, + groupMemberId)) .isInstanceOf(MemberGroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); } @@ -275,7 +276,8 @@ public class MemberGroupCommandServiceTest { //when //then assertThatThrownBy( - () -> groupMemberCommandService.kickGroupMember(notGroupOwnerId, groupId, groupMemberId)) + () -> groupMemberCommandService.kickGroupMember(notGroupOwnerId, groupId, + groupMemberId)) .isInstanceOf(NoGroupAuthorityException.class) .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); } @@ -294,7 +296,8 @@ public class MemberGroupCommandServiceTest { //when //then assertThatThrownBy( - () -> groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, notExistGroupMemberId)) + () -> groupMemberCommandService.kickGroupMember(groupOwnerId, groupId, + notExistGroupMemberId)) .isInstanceOf(MemberGroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_MEMBER_NOT_FOUND_ERROR.getMessage()); } From 37f4bc4b4ba7a2aaef2ef6c8ee8f5e19fe801ec6 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Tue, 21 May 2024 00:38:38 +0900 Subject: [PATCH 045/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=A0=84=20=EC=B9=9C=EA=B5=AC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../friend/api/query/FriendQueryApi.java | 25 ++++++++++ .../api/query/FriendQueryApiController.java | 25 +++++++++- .../FriendBeforeGroupInviteRequest.java | 20 ++++++++ .../MemberFriendQueryRepository.java | 3 ++ .../MemberFriendQueryRepositoryImpl.java | 50 +++++++++++++++---- .../service/query/FriendQueryService.java | 7 +++ 6 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java index 4440fe8d5..3813489e0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java @@ -39,6 +39,31 @@ ResponseEntity> findFriends( ZonedDateTime createdAt ); + @Operation( + summary = "그룹 초대 전 친구 목록 조회", + description = "그룹에 초대할 수 있는 사용자의 소셜 친구 목록을 보여준다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"friend"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findFriendsBeforeGroupInvite( + Long memberId, + + @Parameter(in = ParameterIn.QUERY, description = "초대할 그룹 ID", required = true) + Long groupId, + + @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) + int size, + + @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) + ZonedDateTime createdAt + ); + @Operation( summary = "소셜 친구 요청 목록 조회", description = "사용자의 소셜 친구 요청 목록을 보여준다. 수락 대기 중인 요청만 해당한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java index dd27955a3..4e990d0ae 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; +import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; @@ -52,6 +53,28 @@ public ResponseEntity> findFriends( ); } + @GetMapping("/before/group_invite") + @Override + public ResponseEntity> findFriendsBeforeGroupInvite( + @AuthenticationPrincipal final Long memberId, + @RequestParam(value = "group_id") final Long groupId, + @RequestParam(defaultValue = "20", value = "size") final int size, + @RequestParam(value = "created_at") final ZonedDateTime createdAt + ) { + final FriendBeforeGroupInviteRequest request = FriendBeforeGroupInviteRequest.of(memberId, + groupId, size, createdAt); + + final Slice friendsSlice = friendQueryService.findFriendsBeforeGroupInviteSlice( + request); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + FriendsSliceResponse.createOf(friendsSlice.getContent(), friendsSlice.hasNext()) + ) + ); + } + @GetMapping( value = "/requests", produces = {"application/json"} @@ -62,7 +85,7 @@ public ResponseEntity> findFriendRequests( @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - Slice friendRequestsSlice = friendQueryService.findFriendRequestsSlice( + final Slice friendRequestsSlice = friendQueryService.findFriendRequestsSlice( memberId, size, createdAt); return ResponseEntity.ok( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java new file mode 100644 index 000000000..2b5e5374c --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java @@ -0,0 +1,20 @@ +package site.timecapsulearchive.core.domain.friend.data.request; + +import java.time.ZonedDateTime; + +public record FriendBeforeGroupInviteRequest( + Long memberId, + Long groupId, + int size, + ZonedDateTime createdAt +) { + public static FriendBeforeGroupInviteRequest of( + final Long memberId, + final Long groupId, + final int size, + final ZonedDateTime createdAt + ) { + return new FriendBeforeGroupInviteRequest(memberId, groupId, size, createdAt); + } + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java index fa0f1df47..2df3dc923 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java @@ -7,6 +7,7 @@ import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; +import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; public interface MemberFriendQueryRepository { @@ -34,4 +35,6 @@ Optional findFriendsByTag( ); List findFriendIdsByOwnerId(final Long memberId); + + Slice findFriendsBeforeGroupInvite(final FriendBeforeGroupInviteRequest request); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 70fcbcd9e..5804b2d36 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -1,8 +1,10 @@ package site.timecapsulearchive.core.domain.friend.repository.member_friend; +import static com.querydsl.jpa.JPAExpressions.select; import static site.timecapsulearchive.core.domain.friend.entity.QFriendInvite.friendInvite; import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; +import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -17,6 +19,7 @@ import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; +import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.entity.QFriendInvite; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @@ -48,12 +51,46 @@ public Slice findFriendsSlice( .limit(size + 1) .fetch(); - final boolean hasNext = friends.size() > size; + return getFriendSummaryDtos(size, friends); + } + + private Slice getFriendSummaryDtos(final int size, + final List friendSummaryDtos) { + final boolean hasNext = friendSummaryDtos.size() > size; if (hasNext) { - friends.remove(size); + friendSummaryDtos.remove(size); } - return new SliceImpl<>(friends, Pageable.ofSize(size), hasNext); + return new SliceImpl<>(friendSummaryDtos, Pageable.ofSize(size), hasNext); + } + + public Slice findFriendsBeforeGroupInvite( + final FriendBeforeGroupInviteRequest request) { + final List friends = jpaQueryFactory + .select( + Projections.constructor( + FriendSummaryDto.class, + memberFriend.friend.id, + memberFriend.friend.profileUrl, + memberFriend.friend.nickname, + memberFriend.createdAt + ) + ) + .from(memberFriend) + .innerJoin(member).on(memberFriend.owner.id.eq(member.id)) + .innerJoin(member).on(memberFriend.friend.id.eq(member.id)) + .where(memberFriend.owner.id.eq(request.memberId()) + .and(memberFriend.createdAt.lt(request.createdAt())) + .and(memberFriend.friend.id.notIn( + select(memberGroup.member.id) + .from(memberGroup) + .where(memberGroup.group.id.eq(request.groupId())) + )) + ) + .limit(request.size() + 1) + .fetch(); + + return getFriendSummaryDtos(request.size(), friends); } public Slice findFriendRequestsSlice( @@ -77,12 +114,7 @@ public Slice findFriendRequestsSlice( .limit(size + 1) .fetch(); - final boolean hasNext = friends.size() > size; - if (hasNext) { - friends.remove(size); - } - - return new SliceImpl<>(friends, Pageable.ofSize(size), hasNext); + return getFriendSummaryDtos(size, friends); } public List findFriendsByPhone( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index 862f81d84..d13efda75 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -9,6 +9,7 @@ import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; +import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; @@ -29,6 +30,11 @@ public Slice findFriendsSlice( return memberFriendRepository.findFriendsSlice(memberId, size, createdAt); } + public Slice findFriendsBeforeGroupInviteSlice( + final FriendBeforeGroupInviteRequest request) { + return memberFriendRepository.findFriendsBeforeGroupInvite(request); + } + public Slice findFriendRequestsSlice( final Long memberId, final int size, @@ -54,4 +60,5 @@ public SearchTagFriendSummaryResponse searchFriend(final Long memberId, final St return friendSummaryDto.toResponse(); } + } From 47c13d3dc3410ec88bfdf3417b155b691d9817c3 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Tue, 21 May 2024 01:51:09 +0900 Subject: [PATCH 046/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=9B=84=20=EC=B4=88=EB=8C=80=EB=A5=BC=20?= =?UTF-8?q?=EB=B2=8C=ED=81=AC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/command/GroupCommandService.java | 2 +- .../member/repository/MemberRepository.java | 3 ++ .../api/command/MemberGroupCommandApi.java | 7 +-- .../MemberGroupCommandApiController.java | 19 ++++---- .../data/request/SendGroupRequest.java | 21 +++++++++ .../exception/MemberGroupOverException.java | 11 +++++ .../GroupInviteQueryRepository.java | 2 +- .../GroupInviteQueryRepositoryImpl.java | 9 ++-- .../service/MemberGroupCommandService.java | 23 ++++++---- .../core/global/error/ErrorCode.java | 1 + .../fixture/dto/MemberGroupDtoFixture.java | 11 +++++ .../MemberGroupCommandServiceTest.java | 46 ++++++++++--------- 12 files changed, 103 insertions(+), 52 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/request/SendGroupRequest.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupOverException.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/MemberGroupDtoFixture.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index 7b274330f..1c78454db 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -53,7 +53,7 @@ public void createGroup(final Long memberId, final GroupCreateDto dto) { protected void doInTransactionWithoutResult(TransactionStatus status) { groupRepository.save(group); memberGroupRepository.save(memberGroup); - groupInviteRepository.bulkSave(memberId, dto.targetIds()); + groupInviteRepository.bulkSave(memberId, group.getId(), dto.targetIds()); } }); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java index 02c68c260..2d7d5f2bd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.member.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -16,6 +17,8 @@ public interface MemberRepository extends Repository, MemberQueryR Optional findMemberById(Long memberId); + List findMemberByIdIsIn(List memberIds); + @Modifying(clearAutomatically = true) @Query("UPDATE Member m SET m.fcmToken = :fcmToken WHERE m.id = :memberId") int updateMemberFCMToken(@Param("memberId") Long memberId, @Param("fcmToken") String fcmToken); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java index 26e38f030..3c9cc8711 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import org.springframework.http.ResponseEntity; +import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.error.ErrorResponse; @@ -69,11 +70,7 @@ ResponseEntity> quitGroup( ResponseEntity> inviteGroup( Long memberId, - @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) - Long groupId, - - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) - Long targetId + SendGroupRequest sendGroupRequest ); @Operation( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java index 0d497a132..6af6bce41 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java @@ -6,8 +6,10 @@ 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.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupCommandService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -17,7 +19,7 @@ @RequiredArgsConstructor public class MemberGroupCommandApiController implements MemberGroupCommandApi { - private final MemberGroupCommandService MemberGroupCommandService; + private final MemberGroupCommandService memberGroupCommandService; @DeleteMapping(value = "/{group_id}/members/quit") @Override @@ -25,19 +27,18 @@ public ResponseEntity> quitGroup( @AuthenticationPrincipal final Long memberId, @PathVariable("group_id") final Long groupId ) { - MemberGroupCommandService.quitGroup(memberId, groupId); + memberGroupCommandService.quitGroup(memberId, groupId); return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } - @PostMapping(value = "/{group_id}/member/{target_id}/invite") + @PostMapping(value = "/invite") @Override public ResponseEntity> inviteGroup( @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId, - @PathVariable("target_id") final Long targetId + @RequestBody final SendGroupRequest sendGroupRequest ) { - MemberGroupCommandService.inviteGroup(memberId, groupId, targetId); + memberGroupCommandService.inviteGroup(memberId, sendGroupRequest); return ResponseEntity.ok( ApiSpec.empty( @@ -53,7 +54,7 @@ public ResponseEntity> acceptGroupInvitation( @PathVariable("group_id") final Long groupId, @PathVariable("target_id") final Long targetId ) { - MemberGroupCommandService.acceptGroupInvite(memberId, groupId, targetId); + memberGroupCommandService.acceptGroupInvite(memberId, groupId, targetId); return ResponseEntity.ok( ApiSpec.empty( @@ -68,7 +69,7 @@ public ResponseEntity> rejectGroupInvitation( @PathVariable("group_id") final Long groupId, @PathVariable("target_id") final Long targetId) { - MemberGroupCommandService.rejectRequestGroup(memberId, groupId, targetId); + memberGroupCommandService.rejectRequestGroup(memberId, groupId, targetId); return ResponseEntity.ok( ApiSpec.empty( @@ -84,7 +85,7 @@ public ResponseEntity> kickGroupMember( @PathVariable("group_id") final Long groupId, @PathVariable("group_member_id") final Long groupMemberId ) { - MemberGroupCommandService.kickGroupMember(memberId, groupId, groupMemberId); + memberGroupCommandService.kickGroupMember(memberId, groupId, groupMemberId); return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/request/SendGroupRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/request/SendGroupRequest.java new file mode 100644 index 000000000..cb7c9300f --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/request/SendGroupRequest.java @@ -0,0 +1,21 @@ +package site.timecapsulearchive.core.domain.member_group.data.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.List; + +@Schema(description = "다수의 친구들에게 그룹 추가 요청") +public record SendGroupRequest( + + @Schema(description = "추가할 그룹 Id") + @NotNull + Long groupId, + + @Schema(description = "추가하고 싶은 친구들") + @NotNull + @Size(min = 1, max = 30) + List targetIds +) { + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupOverException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupOverException.java new file mode 100644 index 000000000..5716e8318 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupOverException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.member_group.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class MemberGroupOverException extends BusinessException { + + public MemberGroupOverException() { + super(ErrorCode.GROUP_MEMBER_OVER_ERROR); + } +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java index ecb965fd2..8c7f8bfc4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -4,7 +4,7 @@ public interface GroupInviteQueryRepository { - void bulkSave(final Long groupOwnerId, final List groupMemberIds); + void bulkSave(final Long groupOwnerId, final Long groupId, final List groupMemberIds); List findGroupInviteIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 070926508..9dfb363e6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -23,12 +23,12 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepositor private final JPAQueryFactory jpaQueryFactory; @Override - public void bulkSave(final Long groupOwnerId, final List groupMemberIds) { + public void bulkSave(final Long groupOwnerId, final Long groupId, final List groupMemberIds) { jdbcTemplate.batchUpdate( """ INSERT INTO group_invite ( - group_invite_id, group_owner_id, group_member_id, created_at, updated_at - ) values (?, ?, ?, ?, ?) + group_invite_id, group_owner_id, group_member_id, group_id, created_at, updated_at + ) values (?, ?, ?, ?, ?, ?) """, new BatchPreparedStatementSetter() { @@ -38,8 +38,9 @@ public void setValues(final PreparedStatement ps, final int i) throws SQLExcepti ps.setNull(1, Types.BIGINT); ps.setLong(2, groupOwnerId); ps.setLong(3, groupMember); - ps.setTimestamp(4, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setLong(4, groupId); ps.setTimestamp(5, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setTimestamp(6, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); } @Override diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 4ae38d8a1..2f83f7980 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -14,12 +14,13 @@ import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; -import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; +import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupOverException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; @@ -37,17 +38,19 @@ public class MemberGroupCommandService { private final TransactionTemplate transactionTemplate; private final SocialNotificationManager socialNotificationManager; - public void inviteGroup(final Long memberId, final Long groupId, final Long targetId) { + public void inviteGroup(final Long memberId, final SendGroupRequest sendGroupRequest) { final Member groupOwner = memberRepository.findMemberById(memberId).orElseThrow( MemberNotFoundException::new); - final Member groupMember = memberRepository.findMemberById(targetId).orElseThrow( - MemberNotFoundException::new); + final List friendIds = sendGroupRequest.targetIds(); + final List groupMembers = memberRepository.findMemberByIdIsIn(friendIds); - final Group group = groupRepository.findGroupById(groupId) - .orElseThrow(GroupNotFoundException::new); + if (groupMembers.size() + friendIds.size() > 30) { + throw new MemberGroupOverException(); + } - final GroupInvite groupInvite = GroupInvite.createOf(group, groupOwner, groupMember); + final Group group = groupRepository.findGroupById(sendGroupRequest.groupId()) + .orElseThrow(GroupNotFoundException::new); final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; @@ -55,18 +58,18 @@ public void inviteGroup(final Long memberId, final Long groupId, final Long targ @Override protected void doInTransactionWithoutResult(TransactionStatus status) { summaryDto[0] = memberGroupRepository.findOwnerInMemberGroup( - groupId, memberId).orElseThrow(GroupNotFoundException::new); + sendGroupRequest.groupId(), memberId).orElseThrow(GroupNotFoundException::new); if (!summaryDto[0].isOwner()) { throw new NoGroupAuthorityException(); } - groupInviteRepository.save(groupInvite); + groupInviteRepository.bulkSave(groupOwner.getId(), group.getId(), friendIds); } }); socialNotificationManager.sendGroupInviteMessage(summaryDto[0].nickname(), - summaryDto[0].groupProfileUrl(), List.of(targetId)); + summaryDto[0].groupProfileUrl(), friendIds); } @Transactional 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 299bec8c1..17d9c8108 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 @@ -68,6 +68,7 @@ public enum ErrorCode { GROUP_OWNER_QUIT_ERROR(400, "GROUP-007", "그룹장은 그룹을 탈퇴할 수 없습니다."), GROUP_MEMBER_NOT_FOUND_ERROR(404, "GROUP-008", "그룹에서 해당 멤버를 찾을 수 없습니다."), GROUP_MEMBER_DUPLICATED_ID_ERROR(400, "GROUP-009", "자신을 추방할 수 없습니다."), + GROUP_MEMBER_OVER_ERROR(403, "GROUP-009", "그룹 멤버는 최대 30명까지 입니다."), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/MemberGroupDtoFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/MemberGroupDtoFixture.java new file mode 100644 index 000000000..b206218bc --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/MemberGroupDtoFixture.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.common.fixture.dto; + +import java.util.List; +import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; + +public class MemberGroupDtoFixture { + + public static SendGroupRequest sendGroupRequest(Long groupId, List targetIds) { + return new SendGroupRequest(groupId, targetIds); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 6f3d580bf..27c9017c2 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -11,6 +11,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.transaction.support.TransactionTemplate; @@ -19,11 +20,13 @@ 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.common.fixture.dto.MemberGroupDtoFixture; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; @@ -35,7 +38,7 @@ import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; -public class MemberGroupCommandServiceTest { +class MemberGroupCommandServiceTest { private final MemberRepository memberRepository = mock(MemberRepository.class); private final GroupRepository groupRepository = mock(GroupRepository.class); @@ -59,20 +62,20 @@ public class MemberGroupCommandServiceTest { void 그룹장이_그룹원에게_그룹초대를_하면_그룹초대_알림이_요청된다() { //given Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; + SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(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( + given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( + List.of(MemberFixture.member(2))); + given(groupRepository.findGroupById(request.groupId())).willReturn( + Optional.of(GroupFixture.group())); + given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn( Optional.of(groupOwnerSummaryDto)); //when - groupMemberCommandService.inviteGroup(memberId, groupId, targetId); + groupMemberCommandService.inviteGroup(memberId, request); //then verify(socialNotificationManager, times(1)).sendGroupInviteMessage(anyString(), anyString(), @@ -83,19 +86,18 @@ public class MemberGroupCommandServiceTest { void 그룹장이_그룹초대를_할_때_존재하지_않은_그룹_아이디_이면_예외가_발생한다() { //given Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; + SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(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( + given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( + List.of(MemberFixture.member(2))); + given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn( Optional.empty()); //when //then - assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, groupId, targetId)) + assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, request)) .isInstanceOf(GroupNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_NOT_FOUND_ERROR.getMessage()); } @@ -104,21 +106,21 @@ public class MemberGroupCommandServiceTest { void 그룹장이_아닌_사용자가_그룹원에게_그룹초대를_하면_예외가_발생한다() { //given Long memberId = 1L; - Long groupId = 1L; - Long targetId = 2L; + SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(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)); + given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( + List.of(MemberFixture.member(2))); + given(groupRepository.findGroupById(request.groupId())).willReturn( + Optional.of(GroupFixture.group())); + given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), + memberId)).willReturn(Optional.of(groupOwnerSummaryDto)); //when //then - assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, groupId, targetId)) + assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, request)) .isInstanceOf(NoGroupAuthorityException.class) .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); } From 264e144b0ed0fe3fa916d21ba124f8c5fbfc91b1 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Tue, 21 May 2024 16:34:36 +0900 Subject: [PATCH 047/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B5=9C=EB=8C=80=20=EC=B4=88=EB=8C=80=20=EC=9D=B8=EC=9B=90?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MemberGroupCommandService.java | 10 +------ .../MemberGroupCommandServiceTest.java | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 2f83f7980..44390301d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -39,21 +39,13 @@ public class MemberGroupCommandService { private final SocialNotificationManager socialNotificationManager; public void inviteGroup(final Long memberId, final SendGroupRequest sendGroupRequest) { - final Member groupOwner = memberRepository.findMemberById(memberId).orElseThrow( - MemberNotFoundException::new); - final List friendIds = sendGroupRequest.targetIds(); final List groupMembers = memberRepository.findMemberByIdIsIn(friendIds); - if (groupMembers.size() + friendIds.size() > 30) { throw new MemberGroupOverException(); } - final Group group = groupRepository.findGroupById(sendGroupRequest.groupId()) - .orElseThrow(GroupNotFoundException::new); - final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; - transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { @@ -64,7 +56,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { throw new NoGroupAuthorityException(); } - groupInviteRepository.bulkSave(groupOwner.getId(), group.getId(), friendIds); + groupInviteRepository.bulkSave(memberId, sendGroupRequest.groupId(), friendIds); } }); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 27c9017c2..972661a56 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -32,6 +32,7 @@ import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupOverException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; @@ -63,14 +64,10 @@ class MemberGroupCommandServiceTest { //given Long memberId = 1L; SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); - Member groupOwner = MemberFixture.member(1); GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(true); - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( List.of(MemberFixture.member(2))); - given(groupRepository.findGroupById(request.groupId())).willReturn( - Optional.of(GroupFixture.group())); given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn( Optional.of(groupOwnerSummaryDto)); @@ -82,14 +79,28 @@ class MemberGroupCommandServiceTest { anyList()); } + @Test + void 그룹장이_기존_그룹원을_포함하여_최대_수를_초과하여_그룹초대_요청을_하면_예외가_발생한다() { + //given + Long memberId = 1L; + SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); + + given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( + MemberFixture.membersWithMemberId(1, 40)); + + //when + assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, request)) + .isInstanceOf(MemberGroupOverException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_OVER_ERROR.getMessage()); + + } + @Test void 그룹장이_그룹초대를_할_때_존재하지_않은_그룹_아이디_이면_예외가_발생한다() { //given Long memberId = 1L; SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); - Member groupOwner = MemberFixture.member(1); - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( List.of(MemberFixture.member(2))); given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn( @@ -107,14 +118,10 @@ class MemberGroupCommandServiceTest { //given Long memberId = 1L; SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); - Member groupOwner = MemberFixture.member(1); GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(false); - given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupOwner)); given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( List.of(MemberFixture.member(2))); - given(groupRepository.findGroupById(request.groupId())).willReturn( - Optional.of(GroupFixture.group())); given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn(Optional.of(groupOwnerSummaryDto)); From 1493d84bc1193a07fe72a71aeb8a08db69a76319 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Tue, 21 May 2024 16:43:31 +0900 Subject: [PATCH 048/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B6=94=EB=B0=A9=20Owner=20=ED=99=95=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/member_group/entity/MemberGroup.java | 7 +++++++ .../member_group/exception/GroupQuitException.java | 4 ++-- .../service/MemberGroupCommandService.java | 12 ++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/MemberGroup.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/MemberGroup.java index 50df56c0f..86c539535 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/MemberGroup.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/MemberGroup.java @@ -14,6 +14,7 @@ import lombok.NoArgsConstructor; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.global.entity.BaseEntity; @Entity @@ -51,4 +52,10 @@ public static MemberGroup createGroupOwner(Member member, Group group) { public static MemberGroup createGroupMember(Member member, Group group) { return new MemberGroup(false, member, group); } + + public void checkGroupMemberOwner() { + if (this.isOwner) { + throw new GroupQuitException(); + } + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupQuitException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupQuitException.java index 2bb05ef4c..484325ea3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupQuitException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupQuitException.java @@ -5,7 +5,7 @@ public class GroupQuitException extends BusinessException { - public GroupQuitException(ErrorCode errorCode) { - super(errorCode); + public GroupQuitException() { + super(ErrorCode.GROUP_OWNER_QUIT_ERROR); } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 44390301d..1f8d03d25 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -108,11 +108,8 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void quitGroup(final Long memberId, final Long groupId) { final MemberGroup groupMember = memberGroupRepository.findMemberGroupByMemberIdAndGroupId( - memberId, groupId) - .orElseThrow(MemberGroupNotFoundException::new); - if (groupMember.getIsOwner()) { - throw new GroupQuitException(ErrorCode.GROUP_OWNER_QUIT_ERROR); - } + memberId, groupId).orElseThrow(MemberGroupNotFoundException::new); + groupMember.checkGroupMemberOwner(); memberGroupRepository.delete(groupMember); } @@ -149,9 +146,8 @@ public void kickGroupMember( private void checkGroupOwnership(Long groupOwnerId, Long groupId) { final Boolean isOwner = memberGroupRepository.findIsOwnerByMemberIdAndGroupId( - groupOwnerId, - groupId) - .orElseThrow(MemberGroupNotFoundException::new); + groupOwnerId, groupId).orElseThrow(MemberGroupNotFoundException::new); + if (!isOwner) { throw new NoGroupAuthorityException(); } From 5a5541c01b4225bb702651b54f690c23dd68794a Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 22 May 2024 18:53:54 +0900 Subject: [PATCH 049/195] =?UTF-8?q?feat=20:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/member/entity/CategoryName.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/CategoryName.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/CategoryName.java index 2fbdab7db..803bc6b99 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/CategoryName.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/CategoryName.java @@ -1,5 +1,5 @@ package site.timecapsulearchive.core.domain.member.entity; public enum CategoryName { - CAPSULE_SKIN, FRIEND_REQUEST, FRIEND_ACCEPT, GROUP_REQUEST, GROUP_ACCEPT + CAPSULE_SKIN, FRIEND_REQUEST, FRIEND_ACCEPT, GROUP_INVITE, GROUP_ACCEPT } From a8635f97a38b856dedc495eda3f1e52e8377b377 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 22 May 2024 22:09:57 +0900 Subject: [PATCH 050/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 그룹 초대 유니크 인덱스(그룹장 아이디와 그룹 멤버로만 유니크 판별) 오류 - 그룹 초대 저장시 아이디 저장안되는 오류 --- .../group/service/command/GroupCommandService.java | 4 ++-- .../GroupInviteQueryRepository.java | 2 +- .../GroupInviteQueryRepositoryImpl.java | 13 +++++++++---- .../V27__group_invite_delete_constraint.sql | 13 +++++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 backend/core/src/main/resources/db/migration/V27__group_invite_delete_constraint.sql diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index 113ba2a7c..9f7acde74 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -53,7 +53,7 @@ public void createGroup(final Long memberId, final GroupCreateDto dto) { protected void doInTransactionWithoutResult(TransactionStatus status) { groupRepository.save(group); memberGroupRepository.save(memberGroup); - groupInviteRepository.bulkSave(memberId, dto.targetIds()); + groupInviteRepository.bulkSave(group.getId(), memberId, dto.targetIds()); } }); @@ -73,7 +73,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { * @param groupId 그룹 아이디 */ public void deleteGroup(final Long memberId, final Long groupId) { - final String groupProfilePath = transactionTemplate.execute(transactionStatus -> { + final String groupProfilePath = transactionTemplate.execute(ignored -> { final Group group = groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java index ecb965fd2..935863c5c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -4,7 +4,7 @@ public interface GroupInviteQueryRepository { - void bulkSave(final Long groupOwnerId, final List groupMemberIds); + void bulkSave(final Long groupId, 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/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 070926508..f3e5d3fae 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -23,12 +23,16 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepositor private final JPAQueryFactory jpaQueryFactory; @Override - public void bulkSave(final Long groupOwnerId, final List groupMemberIds) { + public void bulkSave( + final Long groupId, + final Long groupOwnerId, + final List groupMemberIds + ) { jdbcTemplate.batchUpdate( """ INSERT INTO group_invite ( - group_invite_id, group_owner_id, group_member_id, created_at, updated_at - ) values (?, ?, ?, ?, ?) + group_invite_id, group_owner_id, group_member_id, group_id, created_at, updated_at + ) values (?, ?, ?, ?, ?, ?) """, new BatchPreparedStatementSetter() { @@ -38,8 +42,9 @@ public void setValues(final PreparedStatement ps, final int i) throws SQLExcepti ps.setNull(1, Types.BIGINT); ps.setLong(2, groupOwnerId); ps.setLong(3, groupMember); - ps.setTimestamp(4, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setLong(4, groupId); ps.setTimestamp(5, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setTimestamp(6, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); } @Override diff --git a/backend/core/src/main/resources/db/migration/V27__group_invite_delete_constraint.sql b/backend/core/src/main/resources/db/migration/V27__group_invite_delete_constraint.sql new file mode 100644 index 000000000..f9a7e3472 --- /dev/null +++ b/backend/core/src/main/resources/db/migration/V27__group_invite_delete_constraint.sql @@ -0,0 +1,13 @@ +ALTER TABLE group_invite DROP CONSTRAINT fk_group_invite_group_member_id; +ALTER TABLE group_invite DROP CONSTRAINT fk_group_invite_group_owner_id; + +ALTER TABLE group_invite DROP CONSTRAINT unique_owner_member_pair; + +ALTER TABLE group_invite + ADD CONSTRAINT fk_group_invite_group_owner_id FOREIGN KEY (group_owner_id) REFERENCES member (member_id); + +ALTER TABLE group_invite + ADD CONSTRAINT fk_group_invite_group_member_id FOREIGN KEY (group_member_id) REFERENCES member (member_id); + +ALTER TABLE group_invite + ADD CONSTRAINT unique_group_invite UNIQUE (group_id, group_owner_id, group_member_id); \ No newline at end of file From b65d3d8d63a7b8b634bd50e0eb8b09b12f112072 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 23 May 2024 11:29:24 +0900 Subject: [PATCH 051/195] =?UTF-8?q?fix=20:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FriendBeforeGroupInviteRequest.java | 1 + .../MemberFriendQueryRepository.java | 3 +- .../domain/group/data/dto/GroupDetailDto.java | 4 +- .../group/data/dto/GroupSummaryDto.java | 2 +- .../data/response/GroupsSliceResponse.java | 8 +-- .../api/query/MemberGroupQueryApi.java | 39 ++++++++++++++ .../query/MemberGroupQueryApiController.java | 53 +++++++++++++++++++ .../data/dto/GroupInviteSummaryDto.java | 32 +++++++++++ .../data/{ => dto}/GroupOwnerSummaryDto.java | 2 +- .../response/GroupInviteSummaryResponse.java | 36 +++++++++++++ .../response/GroupInviteSummaryResponses.java | 26 +++++++++ .../GroupInviteQueryRepository.java | 9 ++++ .../GroupInviteQueryRepositoryImpl.java | 40 ++++++++++++++ .../MemberGroupQueryRepository.java | 2 +- .../MemberGroupQueryRepositoryImpl.java | 2 +- .../service/MemberGroupCommandService.java | 8 ++- .../service/MemberGroupQueryService.java | 26 +++++++++ .../common/fixture/dto/GroupDtoFixture.java | 2 +- .../MemberGroupCommandServiceTest.java | 2 +- 19 files changed, 279 insertions(+), 18 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/{ => dto}/GroupOwnerSummaryDto.java (62%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponse.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponses.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java index 2b5e5374c..d563d5568 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/FriendBeforeGroupInviteRequest.java @@ -8,6 +8,7 @@ public record FriendBeforeGroupInviteRequest( int size, ZonedDateTime createdAt ) { + public static FriendBeforeGroupInviteRequest of( final Long memberId, final Long groupId, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java index 2df3dc923..48471570c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java @@ -36,5 +36,6 @@ Optional findFriendsByTag( List findFriendIdsByOwnerId(final Long memberId); - Slice findFriendsBeforeGroupInvite(final FriendBeforeGroupInviteRequest request); + Slice findFriendsBeforeGroupInvite( + final FriendBeforeGroupInviteRequest request); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java index d97da178e..bef034dc6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java @@ -14,8 +14,8 @@ public record GroupDetailDto( List members ) { - public GroupDetailResponse toResponse(Function singlePreSignUrlFunction) { - List members = this.members.stream() + public GroupDetailResponse toResponse(final Function singlePreSignUrlFunction) { + final List members = this.members.stream() .map(GroupMemberDto::toResponse) .toList(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java index a900ff27d..e1a9de247 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java @@ -13,7 +13,7 @@ public record GroupSummaryDto( Boolean isOwner ) { - public GroupSummaryResponse toResponse(Function preSignedUrlFunction) { + public GroupSummaryResponse toResponse(final Function preSignedUrlFunction) { return GroupSummaryResponse.builder() .id(id) .name(groupName) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java index d0f4ad450..d0fd0c1af 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java @@ -16,11 +16,11 @@ public record GroupsSliceResponse( ) { public static GroupsSliceResponse createOf( - List groups, - Boolean hasNext, - Function preSignedUrlFunction + final List groups, + final Boolean hasNext, + final Function preSignedUrlFunction ) { - List responses = groups.stream() + final List responses = groups.stream() .map(dto -> dto.toResponse(preSignedUrlFunction)) .toList(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java new file mode 100644 index 000000000..18e84fcf6 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java @@ -0,0 +1,39 @@ +package site.timecapsulearchive.core.domain.member_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.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.member_group.data.response.GroupInviteSummaryResponses; +import site.timecapsulearchive.core.global.common.response.ApiSpec; + +public interface MemberGroupQueryApi { + + + @Operation( + summary = "그룹 요청 목록 조회", + description = "사용자애게 그룹 초대 요청이 온 그룹 목록을 조회한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"member group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findGroupInvites( + 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/member_group/api/query/MemberGroupQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java new file mode 100644 index 000000000..bbdbe2a36 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java @@ -0,0 +1,53 @@ +package site.timecapsulearchive.core.domain.member_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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponses; +import site.timecapsulearchive.core.domain.member_group.service.MemberGroupQueryService; +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 MemberGroupQueryApiController implements MemberGroupQueryApi { + + private final MemberGroupQueryService memberGroupQueryService; + private final S3PreSignedUrlManager s3PreSignedUrlManager; + + + @GetMapping( + value = "/invites", + produces = {"application/json"} + ) + @Override + public ResponseEntity> findGroupInvites( + @AuthenticationPrincipal final Long memberId, + @RequestParam(defaultValue = "20", value = "size") final int size, + @RequestParam(value = "created_at") final ZonedDateTime createdAt + ) { + final Slice groupInviteSummaryDtos = memberGroupQueryService.findGroupInvites( + memberId, size, createdAt); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + GroupInviteSummaryResponses.createOf( + groupInviteSummaryDtos.getContent(), + groupInviteSummaryDtos.hasNext(), + s3PreSignedUrlManager::getS3PreSignedUrlForGet + ) + ) + ); + } + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java new file mode 100644 index 000000000..140f58ec7 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java @@ -0,0 +1,32 @@ +package site.timecapsulearchive.core.domain.member_group.data.dto; + +import java.time.ZonedDateTime; +import java.util.function.Function; +import lombok.Builder; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponse; + +@Builder +public record GroupInviteSummaryDto( + + Long groupId, + String groupName, + String groupProfileUrl, + String description, + ZonedDateTime createdAt, + String groupOwnerName +) { + + public GroupInviteSummaryResponse toResponse( + final Function preSignedUrlFunction + ) { + return GroupInviteSummaryResponse.builder() + .groupId(groupId) + .groupName(groupName) + .groupProfileUrl(preSignedUrlFunction.apply(groupProfileUrl)) + .description(description) + .createdAt(createdAt) + .groupOwnerName(groupOwnerName) + .build(); + } + +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/GroupOwnerSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupOwnerSummaryDto.java similarity index 62% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/GroupOwnerSummaryDto.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupOwnerSummaryDto.java index 21a19f609..ab0f55961 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/GroupOwnerSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupOwnerSummaryDto.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.member_group.data; +package site.timecapsulearchive.core.domain.member_group.data.dto; public record GroupOwnerSummaryDto( String nickname, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponse.java new file mode 100644 index 000000000..77acf1acf --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponse.java @@ -0,0 +1,36 @@ +package site.timecapsulearchive.core.domain.member_group.data.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.ZonedDateTime; +import lombok.Builder; +import site.timecapsulearchive.core.global.common.response.ResponseMappingConstant; + +@Builder +@Schema(description = "초대온 그룹 요약 정보") +public record GroupInviteSummaryResponse( + + @Schema(description = "그룹 아이디") + Long groupId, + + @Schema(description = "그룹 이름") + String groupName, + + @Schema(description = "그룹 프로필 url") + String groupProfileUrl, + + @Schema(description = "그룹 설명") + String description, + + @Schema(description = "그룹 생성일") + ZonedDateTime createdAt, + + @Schema(description = "그룹장") + String groupOwnerName +) { + + public GroupInviteSummaryResponse { + if (createdAt != null) { + createdAt = createdAt.withZoneSameInstant(ResponseMappingConstant.ZONE_ID); + } + } +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponses.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponses.java new file mode 100644 index 000000000..4dd508058 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponses.java @@ -0,0 +1,26 @@ +package site.timecapsulearchive.core.domain.member_group.data.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import java.util.function.Function; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; + +public record GroupInviteSummaryResponses( + @Schema(description = "초대 온 그룹 요약 정보 리스트") + List responses, + + @Schema(description = "다음 페이지 유무") + Boolean hasNext +) { + + public static GroupInviteSummaryResponses createOf( + final List dtos, + final boolean hasNext, + final Function preSignedUrlFunction + ) { + List groupInviteSummaryResponses = dtos.stream() + .map(dto -> dto.toResponse(preSignedUrlFunction)).toList(); + + return new GroupInviteSummaryResponses(groupInviteSummaryResponses, hasNext); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java index 8c7f8bfc4..7d1725f84 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -1,10 +1,19 @@ package site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository; +import java.time.ZonedDateTime; import java.util.List; +import org.springframework.data.domain.Slice; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; public interface GroupInviteQueryRepository { void bulkSave(final Long groupOwnerId, final Long groupId, final List groupMemberIds); List findGroupInviteIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); + + Slice findGroupInvitesSummary( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 43a2996e2..375751d18 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -1,8 +1,11 @@ package site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository; +import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; +import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QGroupInvite.groupInvite; +import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -11,9 +14,13 @@ import java.time.ZonedDateTime; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; @Repository @RequiredArgsConstructor @@ -62,4 +69,37 @@ public List findGroupInviteIdsByGroupIdAndGroupOwnerId( .where(groupInvite.group.id.eq(groupId).and(groupInvite.groupOwner.id.eq(memberId))) .fetch(); } + + @Override + public Slice findGroupInvitesSummary( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + final List groupInviteSummaryDtos = jpaQueryFactory + .select( + Projections.constructor( + GroupInviteSummaryDto.class, + group.id, + group.groupName, + group.groupProfileUrl, + group.groupDescription, + group.createdAt, + member.nickname + ) + + ) + .from(groupInvite) + .join(groupInvite.group, group) + .join(groupInvite.groupOwner, member).on(groupInvite.groupMember.id.eq(memberId)) + .fetch(); + + final boolean hasNext = groupInviteSummaryDtos.size() > size; + if (hasNext) { + groupInviteSummaryDtos.remove(size); + } + + return new SliceImpl<>(groupInviteSummaryDtos, Pageable.ofSize(size), hasNext); + } + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java index f21112a0a..963047020 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -1,7 +1,7 @@ package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; import java.util.Optional; -import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; public interface MemberGroupQueryRepository { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 6d09460dc..8aa557ac2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -9,7 +9,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; @Repository @RequiredArgsConstructor diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 1f8d03d25..a8acb586e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -13,18 +13,16 @@ 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.domain.member_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; -import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupOverException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; -import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Service @@ -108,7 +106,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { @Transactional public void quitGroup(final Long memberId, final Long groupId) { final MemberGroup groupMember = memberGroupRepository.findMemberGroupByMemberIdAndGroupId( - memberId, groupId).orElseThrow(MemberGroupNotFoundException::new); + memberId, groupId).orElseThrow(MemberGroupNotFoundException::new); groupMember.checkGroupMemberOwner(); memberGroupRepository.delete(groupMember); @@ -146,7 +144,7 @@ public void kickGroupMember( private void checkGroupOwnership(Long groupOwnerId, Long groupId) { final Boolean isOwner = memberGroupRepository.findIsOwnerByMemberIdAndGroupId( - groupOwnerId, groupId).orElseThrow(MemberGroupNotFoundException::new); + groupOwnerId, groupId).orElseThrow(MemberGroupNotFoundException::new); if (!isOwner) { throw new NoGroupAuthorityException(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java new file mode 100644 index 000000000..0bf45099e --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -0,0 +1,26 @@ +package site.timecapsulearchive.core.domain.member_group.service; + +import java.time.ZonedDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberGroupQueryService { + + private final GroupInviteRepository groupInviteRepository; + + public Slice findGroupInvites( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + return groupInviteRepository.findGroupInvitesSummary(memberId, size, createdAt); + } + +} 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 e14b0c1d9..0b673b04b 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.member_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; public class GroupDtoFixture { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 972661a56..b34d891d5 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -25,7 +25,7 @@ import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; -import site.timecapsulearchive.core.domain.member_group.data.GroupOwnerSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; From 2d1332d3b6e60cdceb0e4f709033dedf883e9015 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 23 May 2024 11:44:11 +0900 Subject: [PATCH 052/195] =?UTF-8?q?fix=20:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/query/GroupQueryApiController.java | 7 ++- .../domain/group/data/dto/GroupDetailDto.java | 16 ------ .../group/data/dto/GroupDetailTotalDto.java | 56 +++++++++++++++++++ .../domain/group/data/dto/GroupMemberDto.java | 7 ++- .../data/dto/GroupMemberWithRelationDto.java | 26 +++++++++ .../data/response/GroupDetailResponse.java | 6 ++ .../data/response/GroupMemberResponse.java | 5 +- .../repository/GroupQueryRepository.java | 8 ++- .../repository/GroupQueryRepositoryImpl.java | 31 +++++++++- .../service/query/GroupQueryService.java | 23 ++++---- .../GroupInviteQueryRepositoryImpl.java | 1 - .../MemberGroupQueryRepositoryTest.java | 34 ++++++----- 12 files changed, 167 insertions(+), 53 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java 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 index befb57525..4836f71d9 100644 --- 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 @@ -10,7 +10,7 @@ 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.GroupDetailTotalDto; 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; @@ -36,13 +36,14 @@ public ResponseEntity> findGroupDetailById( @AuthenticationPrincipal final Long memberId, @PathVariable("group_id") final Long groupId ) { - final GroupDetailDto groupDetailDto = groupQueryService.findGroupDetailByGroupId(memberId, + final GroupDetailTotalDto groupDetailTotalDto = groupQueryService.findGroupDetailByGroupId( + memberId, groupId); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - groupDetailDto.toResponse(s3PreSignedUrlManager::getS3PreSignedUrlForGet) + groupDetailTotalDto.toResponse(s3PreSignedUrlManager::getS3PreSignedUrlForGet) ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java index bef034dc6..e80c619f6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java @@ -2,9 +2,6 @@ import java.time.ZonedDateTime; import java.util.List; -import java.util.function.Function; -import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; -import site.timecapsulearchive.core.domain.group.data.response.GroupMemberResponse; public record GroupDetailDto( String groupName, @@ -14,17 +11,4 @@ public record GroupDetailDto( List members ) { - public GroupDetailResponse toResponse(final Function singlePreSignUrlFunction) { - final List members = this.members.stream() - .map(GroupMemberDto::toResponse) - .toList(); - - return GroupDetailResponse.builder() - .groupName(groupName) - .groupDescription(groupDescription) - .groupProfileUrl(singlePreSignUrlFunction.apply(groupProfileUrl)) - .createdAt(createdAt) - .members(members) - .build(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java new file mode 100644 index 000000000..5a83ba17e --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java @@ -0,0 +1,56 @@ +package site.timecapsulearchive.core.domain.group.data.dto; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.function.Function; +import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; +import site.timecapsulearchive.core.domain.group.data.response.GroupMemberResponse; + +public record GroupDetailTotalDto( + String groupName, + String groupDescription, + String groupProfileUrl, + ZonedDateTime createdAt, + Long groupCapsuleTotalCount, + Boolean canGroupEdit, + List members +) { + + public static GroupDetailTotalDto as( + final GroupDetailDto groupDetailDto, + final Long groupCapsuleTotalCount, + final Boolean canGroupEdit, + final List friendIds + ) { + final List membersWithRelation = groupDetailDto.members() + .stream() + .map(dto -> dto.toRelationDto(friendIds)) + .toList(); + + return new GroupDetailTotalDto( + groupDetailDto.groupName(), + groupDetailDto.groupDescription(), + groupDetailDto.groupProfileUrl(), + groupDetailDto.createdAt(), + groupCapsuleTotalCount, + canGroupEdit, + membersWithRelation + ); + } + + public GroupDetailResponse toResponse(final Function singlePreSignUrlFunction) { + List members = this.members.stream() + .map(GroupMemberWithRelationDto::toResponse) + .toList(); + + return GroupDetailResponse.builder() + .groupName(groupName) + .groupDescription(groupDescription) + .groupProfileUrl(singlePreSignUrlFunction.apply(groupProfileUrl)) + .createdAt(createdAt) + .groupCapsuleTotalCount(groupCapsuleTotalCount) + .canGroupEdit(canGroupEdit) + .members(members) + .build(); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java index 09a03c5e1..ab9ae4506 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java @@ -1,6 +1,6 @@ package site.timecapsulearchive.core.domain.group.data.dto; -import site.timecapsulearchive.core.domain.group.data.response.GroupMemberResponse; +import java.util.List; public record GroupMemberDto( Long memberId, @@ -10,13 +10,14 @@ public record GroupMemberDto( Boolean isOwner ) { - public GroupMemberResponse toResponse() { - return GroupMemberResponse.builder() + public GroupMemberWithRelationDto toRelationDto(final List friendIds) { + return GroupMemberWithRelationDto.builder() .memberId(memberId) .profileUrl(profileUrl) .nickname(nickname) .tag(tag) .isOwner(isOwner) + .isFriend(friendIds.contains(memberId)) .build(); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java new file mode 100644 index 000000000..29f3dbf12 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java @@ -0,0 +1,26 @@ +package site.timecapsulearchive.core.domain.group.data.dto; + +import lombok.Builder; +import site.timecapsulearchive.core.domain.group.data.response.GroupMemberResponse; + +@Builder +public record GroupMemberWithRelationDto( + Long memberId, + String profileUrl, + String nickname, + String tag, + Boolean isOwner, + Boolean isFriend +) { + + public GroupMemberResponse toResponse() { + return GroupMemberResponse.builder() + .memberId(memberId) + .profileUrl(profileUrl) + .nickname(nickname) + .tag(tag) + .isOwner(isOwner) + .isFriend(isFriend) + .build(); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java index 98b21892d..ed3852aa8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java @@ -22,6 +22,12 @@ public record GroupDetailResponse( @Schema(description = "그룹 생성일") ZonedDateTime createdAt, + @Schema(description = "그룹 캡슐의 총 개수") + Long groupCapsuleTotalCount, + + @Schema(description = "그룹 수정 권한") + Boolean canGroupEdit, + @Schema(description = "그룹원 리스트") List members ) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberResponse.java index db025b957..440ff6e3d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberResponse.java @@ -20,7 +20,10 @@ public record GroupMemberResponse( String tag, @Schema(description = "그룹장 여부") - Boolean isOwner + Boolean isOwner, + + @Schema(description = "친구 여부") + Boolean isFriend ) { } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index a19bcb478..c3cbd537e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.group.repository; import java.time.ZonedDateTime; +import java.util.List; import java.util.Optional; import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; @@ -15,6 +16,11 @@ Slice findGroupsSlice( final ZonedDateTime createdAt ); - Optional findGroupDetailByGroupId(final Long groupId); + Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId); + Long findGroupCapsuleCount(final Long groupId); + + Boolean findGroupEditPermission(final Long groupId, final Long memberId); + + List findFriendIds(final List groupMemberIds, final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index ff74a6692..22fa95b4c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -2,6 +2,8 @@ import static com.querydsl.core.group.GroupBy.groupBy; import static com.querydsl.core.group.GroupBy.list; +import static site.timecapsulearchive.core.domain.capsule.entity.QCapsule.capsule; +import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; @@ -57,13 +59,14 @@ public Slice findGroupsSlice( return new SliceImpl<>(groups, Pageable.ofSize(size), groups.size() > size); } - public Optional findGroupDetailByGroupId(final Long groupId) { + public Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, + final Long memberId) { return Optional.ofNullable( jpaQueryFactory .selectFrom(group) - .join(memberGroup).on(memberGroup.group.id.eq(group.id)) + .join(memberGroup).on(memberGroup.group.id.eq(groupId)) .join(member).on(member.id.eq(memberGroup.member.id)) - .where(group.id.eq(groupId)) + .where(member.id.ne(memberId)) .transform( groupBy(group.id).as( Projections.constructor( @@ -88,4 +91,26 @@ public Optional findGroupDetailByGroupId(final Long groupId) { .get(groupId) ); } + + public Long findGroupCapsuleCount(final Long groupId) { + return jpaQueryFactory.select(capsule.count()) + .from(capsule) + .where(capsule.group.id.eq(groupId)) + .fetchOne(); + } + + public Boolean findGroupEditPermission(final Long groupId, final Long memberId) { + return jpaQueryFactory.select(memberGroup.isOwner) + .from(memberGroup) + .where(memberGroup.group.id.eq(groupId).and(memberGroup.member.id.eq(memberId))) + .fetchOne(); + } + + public List findFriendIds(final List groupMemberIds, final Long memberId) { + return jpaQueryFactory.select(memberFriend.friend.id) + .from(memberFriend) + .where( + memberFriend.friend.id.in(groupMemberIds).and(memberFriend.owner.id.eq(memberId))) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index 29049385f..56571ad6f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -1,11 +1,14 @@ package site.timecapsulearchive.core.domain.group.service.query; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; 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; @@ -31,18 +34,18 @@ public Slice findGroupsSlice( return groupRepository.findGroupsSlice(memberId, size, createdAt); } - public GroupDetailDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { - final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupId(groupId) - .orElseThrow(GroupNotFoundException::new); + public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { + final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupIdAndMemberId(groupId, + memberId).orElseThrow(GroupNotFoundException::new); - final boolean isGroupMember = groupDetailDto.members() - .stream() - .anyMatch(m -> m.memberId().equals(memberId)); + final Long groupCapsuleCount = groupRepository.findGroupCapsuleCount(groupId); + final Boolean canGroupEdit = groupRepository.findGroupEditPermission(groupId, memberId); - if (!isGroupMember) { - throw new GroupNotFoundException(); - } + final List groupMemberIds = groupDetailDto.members().stream() + .map(GroupMemberDto::memberId) + .toList(); + final List friendIds = groupRepository.findFriendIds(groupMemberIds, memberId); - return groupDetailDto; + return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, canGroupEdit, friendIds); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 375751d18..21b021de9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -29,7 +29,6 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepositor private final JdbcTemplate jdbcTemplate; private final JPAQueryFactory jpaQueryFactory; - public void bulkSave(final Long groupOwnerId, final Long groupId, final List groupMemberIds) { jdbcTemplate.batchUpdate( """ 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 eadfa29a5..fcb0ab5ab 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 @@ -23,6 +23,7 @@ import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberGroupFixture; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; @@ -63,7 +64,8 @@ void setup(@Autowired EntityManager entityManager) { entityManager.persist(group); groups.add(group); } - //사용자가 그룹장인 그룹 + + //사용자가 그룹장인 그룹아이디 ownerGroupId = groups.get(0).getId(); //그룹원들 @@ -148,22 +150,22 @@ void setup(@Autowired EntityManager entityManager) { } @Test - void 그룹_아이디로_그룹을_조회하면_그룹_상세가_반환된다() { + void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_상세가_반환된다() { //given //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupId( - ownerGroupId).orElseThrow(); + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + ownerGroupId, memberId).orElseThrow(); //then assertThat(groupDetail).isNotNull(); } @Test - void 그룹_아이디로_그룹을_조회하면_그룹_정보를_볼_수_있다() { + void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_정보를_볼_수_있다() { //given //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupId( - ownerGroupId).orElseThrow(); + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + ownerGroupId, memberId).orElseThrow(); //then SoftAssertions.assertSoftly(softly -> { @@ -175,11 +177,11 @@ void setup(@Autowired EntityManager entityManager) { } @Test - void 그룹_아이디로_그룹을_조회하면_그룹원들의_정보를_볼_수_있다() { + void 그룹_아이디와_멤버_아이디_그룹을_조회하면_그룹원들의_정보를_볼_수_있다() { //given //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupId( - ownerGroupId).orElseThrow(); + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + ownerGroupId, memberId).orElseThrow(); //then SoftAssertions.assertSoftly(softly -> { @@ -195,16 +197,18 @@ void setup(@Autowired EntityManager entityManager) { } @Test - void 그룹_아이디로_그룹을_조회하면_한_명의_그룹장만_존재한다() { + void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_본인은_포함되어_조회되지_않는다() { //given //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupId( - ownerGroupId).orElseThrow(); + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + ownerGroupId, memberId).orElseThrow(); + + List groupMemberDtos = groupDetail.members(); //then SoftAssertions.assertSoftly(softly -> { - assertThat(groupDetail.members()).satisfiesOnlyOnce( - m -> assertThat(m.isOwner()).isTrue()); + softly.assertThat(groupMemberDtos) + .noneMatch(member -> member.memberId().equals(memberId)); }); } } \ No newline at end of file From 541a45864682a8f4da696c32959c4bc8f95421ba Mon Sep 17 00:00:00 2001 From: hong seokho Date: Tue, 21 May 2024 08:04:19 +0900 Subject: [PATCH 053/195] =?UTF-8?q?feat=20:=20=EC=BA=A1=EC=8A=90=20?= =?UTF-8?q?=EB=B3=B4=EB=AC=BC=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/capsule/entity/CapsuleType.java | 1 + .../repository/CapsuleQueryRepository.java | 18 ++++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/CapsuleType.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/CapsuleType.java index e3b8b14be..0f6dc063a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/CapsuleType.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/CapsuleType.java @@ -4,5 +4,6 @@ public enum CapsuleType { SECRET, PUBLIC, GROUP, + TREASURE, ALL } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java index 505e9371e..700a7d637 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepository.java @@ -55,17 +55,16 @@ public List findARCapsuleSummaryDtosByCurrentLocation .from(capsule) .join(capsule.capsuleSkin, capsuleSkin) .join(capsule.member, member) - .where(ST_Contains(mbr, capsule.point).and(capsule.member.id.eq(memberId) - .and(eqCapsuleType(capsuleType)))) + .where(ST_Contains(mbr, capsule.point).and(capsuleFilter(capsuleType, memberId))) .fetch(); } - private BooleanExpression eqCapsuleType(CapsuleType capsuleType) { - if (capsuleType.equals(CapsuleType.ALL)) { - return null; - } - - return capsule.type.eq(capsuleType); + private BooleanExpression capsuleFilter(CapsuleType capsuleType, Long memberId) { + return switch (capsuleType) { + case ALL -> capsule.member.id.eq(memberId); + case TREASURE -> capsule.type.eq(capsuleType); + default -> capsule.type.eq(capsuleType).and(capsule.member.id.eq(memberId)); + }; } /** @@ -93,8 +92,7 @@ public List findCapsuleSummaryDtosByCurrentLocationAndC .from(capsule) .join(capsule.capsuleSkin, capsuleSkin) .join(capsule.member, member) - .where(ST_Contains(mbr, capsule.point).and(capsule.member.id.eq(memberId)) - .and(eqCapsuleType(capsuleType))) + .where(ST_Contains(mbr, capsule.point).and(capsuleFilter(capsuleType, memberId))) .fetch(); } From 7d62a1fbba06f55002560810002fb2910aa39372 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Tue, 21 May 2024 08:04:45 +0900 Subject: [PATCH 054/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CapsuleQueryRepositoryTest.java | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepositoryTest.java index f3d6a84fd..f4fa3bec4 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleQueryRepositoryTest.java @@ -1,7 +1,5 @@ package site.timecapsulearchive.core.domain.capsule.generic_capsule.repository; -import static org.assertj.core.api.Assertions.assertThat; - import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import java.util.ArrayList; @@ -134,10 +132,10 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); - assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); + softly.assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } @@ -154,10 +152,10 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); - assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); + softly.assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } @@ -174,10 +172,10 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); - assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); + softly.assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } @@ -194,10 +192,10 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); - assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch(c -> c.capsuleType().equals(capsuleType)); + softly.assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } @@ -214,14 +212,14 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch( + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch( c -> c.capsuleType().equals(CapsuleType.PUBLIC) || c.capsuleType().equals(CapsuleType.SECRET) || c.capsuleType().equals(CapsuleType.GROUP) ); - assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } @@ -238,14 +236,14 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch( + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch( c -> c.capsuleType().equals(CapsuleType.PUBLIC) || c.capsuleType().equals(CapsuleType.SECRET) || c.capsuleType().equals(CapsuleType.GROUP) ); - assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).allMatch(c -> myCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } @@ -261,10 +259,10 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch(c -> friendCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> c.capsuleType().equals(CapsuleType.PUBLIC)); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch(c -> friendCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> c.capsuleType().equals(CapsuleType.PUBLIC)); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } @@ -280,10 +278,10 @@ void setup(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - assertThat(capsules).isNotEmpty(); - assertThat(capsules).allMatch(c -> friendCapsuleIds.contains(c.id())); - assertThat(capsules).allMatch(c -> c.capsuleType().equals(CapsuleType.PUBLIC)); - assertThat(capsules).allMatch(c -> mbr.contains(c.point())); + softly.assertThat(capsules).isNotEmpty(); + softly.assertThat(capsules).allMatch(c -> friendCapsuleIds.contains(c.id())); + softly.assertThat(capsules).allMatch(c -> c.capsuleType().equals(CapsuleType.PUBLIC)); + softly.assertThat(capsules).allMatch(c -> mbr.contains(c.point())); }); } } \ No newline at end of file From e7b7a1a281abb1fe2958eefee31a241e908faf72 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 25 May 2024 19:30:42 +0900 Subject: [PATCH 055/195] =?UTF-8?q?chore=20:=20.gitignore=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 723ef36f4..91cd78597 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.idea \ No newline at end of file +.idea + +data \ No newline at end of file From 6c945c7d85d9d7ecfe92ef6a7b0757bdcc1658b6 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 27 May 2024 13:46:54 +0900 Subject: [PATCH 056/195] =?UTF-8?q?refactor:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=98=88=EC=99=B8=20=EC=83=81=ED=99=A9=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/command/MemberGroupCommandApi.java | 13 ++++++++++--- ...ion.java => GroupMemberCountLimitException.java} | 6 +++--- .../service/MemberGroupCommandService.java | 4 ++-- .../core/global/error/ErrorCode.java | 2 +- .../service/MemberGroupCommandServiceTest.java | 6 +++--- 5 files changed, 19 insertions(+), 12 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/{MemberGroupOverException.java => GroupMemberCountLimitException.java} (55%) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java index 3c9cc8711..e193e273e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java @@ -65,7 +65,12 @@ ResponseEntity> quitGroup( @ApiResponse( responseCode = "202", description = "처리 시작" - ) + ), + @ApiResponse( + responseCode = "400", + description = "기존의 그룹 멤버와 요청 멤버의 수가 30을 넘은 경우 예외가 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), }) ResponseEntity> inviteGroup( Long memberId, @@ -86,11 +91,13 @@ ResponseEntity> inviteGroup( ), @ApiResponse( responseCode = "404", - description = "그룹 초대 찾기 실패" + description = "그룹 초대 찾기 실패", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ), @ApiResponse( responseCode = "500", - description = "외부 API 요청 실패" + description = "외부 API 요청 실패", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) }) ResponseEntity> acceptGroupInvitation( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupOverException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupMemberCountLimitException.java similarity index 55% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupOverException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupMemberCountLimitException.java index 5716e8318..a5392d2a6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/MemberGroupOverException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/exception/GroupMemberCountLimitException.java @@ -3,9 +3,9 @@ import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.BusinessException; -public class MemberGroupOverException extends BusinessException { +public class GroupMemberCountLimitException extends BusinessException { - public MemberGroupOverException() { - super(ErrorCode.GROUP_MEMBER_OVER_ERROR); + public GroupMemberCountLimitException() { + super(ErrorCode.GROUP_MEMBER_COUNT_LIMIT_ERROR); } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index a8acb586e..c902bb6d5 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -19,7 +19,7 @@ import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; -import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupOverException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; @@ -40,7 +40,7 @@ public void inviteGroup(final Long memberId, final SendGroupRequest sendGroupReq final List friendIds = sendGroupRequest.targetIds(); final List groupMembers = memberRepository.findMemberByIdIsIn(friendIds); if (groupMembers.size() + friendIds.size() > 30) { - throw new MemberGroupOverException(); + throw new GroupMemberCountLimitException(); } final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; 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 17d9c8108..2f97178f9 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 @@ -68,7 +68,7 @@ public enum ErrorCode { GROUP_OWNER_QUIT_ERROR(400, "GROUP-007", "그룹장은 그룹을 탈퇴할 수 없습니다."), GROUP_MEMBER_NOT_FOUND_ERROR(404, "GROUP-008", "그룹에서 해당 멤버를 찾을 수 없습니다."), GROUP_MEMBER_DUPLICATED_ID_ERROR(400, "GROUP-009", "자신을 추방할 수 없습니다."), - GROUP_MEMBER_OVER_ERROR(403, "GROUP-009", "그룹 멤버는 최대 30명까지 입니다."), + GROUP_MEMBER_COUNT_LIMIT_ERROR(400, "GROUP-009", "그룹 멤버는 최대 30명까지 입니다."), //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index b34d891d5..58cfe0eee 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -32,7 +32,7 @@ import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; -import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupOverException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; @@ -90,8 +90,8 @@ class MemberGroupCommandServiceTest { //when assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, request)) - .isInstanceOf(MemberGroupOverException.class) - .hasMessageContaining(ErrorCode.GROUP_MEMBER_OVER_ERROR.getMessage()); + .isInstanceOf(GroupMemberCountLimitException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_COUNT_LIMIT_ERROR.getMessage()); } From c81719c62e6d0fccd4b170ba0b397170e78701bb Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 27 May 2024 14:13:28 +0900 Subject: [PATCH 057/195] =?UTF-8?q?refact:=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EB=B0=9B=EC=9D=80=20=EA=B7=B8=EB=A3=B9=20=EC=B4=88=EB=8C=80=20?= =?UTF-8?q?=EC=9A=94=EC=95=BD=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=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 --- .../groupInviteRepository/GroupInviteQueryRepositoryImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 21b021de9..b31ac6426 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -4,6 +4,7 @@ import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QGroupInvite.groupInvite; +import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -91,6 +92,8 @@ public Slice findGroupInvitesSummary( .from(groupInvite) .join(groupInvite.group, group) .join(groupInvite.groupOwner, member).on(groupInvite.groupMember.id.eq(memberId)) + .where(groupInvite.createdAt.lt(createdAt)) + .limit(size + 1) .fetch(); final boolean hasNext = groupInviteSummaryDtos.size() > size; From 1e8378fc74ba9b7e7f18e1cb73c363b58199b637 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 27 May 2024 15:14:16 +0900 Subject: [PATCH 058/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EB=AA=A9=EB=A1=9D=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=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/GroupFixture.java | 12 ++ .../GroupCapsuleOpenQueryRepositoryTest.java | 2 +- .../GroupInviteQueryRepositoryTest.java | 119 ++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) rename backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/{ => group_capsule}/repository/GroupCapsuleOpenQueryRepositoryTest.java (98%) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupFixture.java index 615613e58..8f47d6b04 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupFixture.java @@ -1,5 +1,7 @@ package site.timecapsulearchive.core.common.fixture.domain; +import java.util.List; +import java.util.stream.IntStream; import site.timecapsulearchive.core.domain.group.entity.Group; public class GroupFixture { @@ -16,4 +18,14 @@ public static Group group() { .groupProfileUrl("test_group") .build(); } + + public static List groups(int startDataPrefix, int count) { + return IntStream.range(startDataPrefix, count) + .mapToObj(idx -> Group.builder() + .groupName(idx + "test_group_name") + .groupDescription(idx + "test_group_description") + .groupProfileUrl(idx + "test+group_profile_url") + .build()) + .toList(); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java similarity index 98% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java index 5c6459400..e7f245f2d 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.capsule.repository; +package site.timecapsulearchive.core.domain.capsule.group_capsule.repository; import static org.assertj.core.api.Assertions.assertThat; diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java new file mode 100644 index 000000000..52048ddda --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -0,0 +1,119 @@ +package site.timecapsulearchive.core.domain.member_group.repository; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import java.time.ZonedDateTime; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Slice; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.TestConstructor; +import org.springframework.test.context.TestConstructor.AutowireMode; +import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.common.RepositoryTest; +import site.timecapsulearchive.core.common.fixture.domain.GroupFixture; +import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; +import site.timecapsulearchive.core.domain.group.entity.Group; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; +import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteQueryRepository; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteQueryRepositoryImpl; + +@TestConstructor(autowireMode = AutowireMode.ALL) +class GroupInviteQueryRepositoryTest extends RepositoryTest { + + private static final int MAX_GROUP_COUNT = 2; + private final GroupInviteQueryRepository groupInviteRepository; + private Member groupMember; + + GroupInviteQueryRepositoryTest( + JdbcTemplate jdbcTemplate, + JPAQueryFactory jpaQueryFactory + ) { + this.groupInviteRepository = new GroupInviteQueryRepositoryImpl( + jdbcTemplate, jpaQueryFactory); + } + + @Transactional + @BeforeEach + void setUp(@Autowired EntityManager entityManager) { + // 그룹 초대 할 그룹장들 + List groupOwners = MemberFixture.members(0, MAX_GROUP_COUNT); + groupOwners.forEach(entityManager::persist); + + //그룹 초대 올 그룹원 + groupMember = MemberFixture.member(2); + entityManager.persist(groupMember); + + // 그룹들 + List groups = GroupFixture.groups(0, MAX_GROUP_COUNT); + groups.forEach(entityManager::persist); + + // 그룹원에게 초대온 그룹 초대들 + for (int i = 0; i < MAX_GROUP_COUNT; i++) { + GroupInvite groupInvite = GroupInvite.createOf(groups.get(i), groupOwners.get(i), + groupMember); + entityManager.persist(groupInvite); + } + } + + @Test + void 사용자는_자신에게_온_그룹_초대_목록을_조회할_수_있다() { + //given + Long memberId = groupMember.getId(); + int size = 1; + ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); + + //when + Slice groupInvitesSummary = groupInviteRepository.findGroupInvitesSummary( + memberId, size, createAt); + + //then + assertThat(groupInvitesSummary.getContent()).isNotNull(); + } + + @Test + void 사용자는_자신에게_온_그룹_초대_목록에서_그룹_정보와_그룹장을_알_수_있다() { + //given + Long memberId = groupMember.getId(); + int size = 1; + ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); + + //when + Slice groupInvitesSummary = groupInviteRepository.findGroupInvitesSummary( + memberId, size, createAt); + + //then + SoftAssertions.assertSoftly(softly -> { + Assertions.assertThat(groupInvitesSummary).allMatch(g -> !g.groupName().isEmpty()); + Assertions.assertThat(groupInvitesSummary) + .allMatch(g -> !g.groupProfileUrl().isEmpty()); + Assertions.assertThat(groupInvitesSummary).allMatch(g -> !g.description().isEmpty()); + Assertions.assertThat(groupInvitesSummary).allMatch(g -> !g.groupOwnerName().isEmpty()); + }); + } + + @Test + void 사용자는_조회한_그룹_초대_목록_후_그룹_초대_존재_여부를_확인_할_수_있다() { + //given + Long memberId = groupMember.getId(); + int size = 1; + ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); + + //when + Slice groupInvitesSummary = groupInviteRepository.findGroupInvitesSummary( + memberId, size, createAt); + + //then + assertThat(groupInvitesSummary.hasNext()).isTrue(); + } + + +} From 2751de4c8213acf858267fa345412ce3805f15fa Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 27 May 2024 15:27:09 +0900 Subject: [PATCH 059/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=EC=82=AC=ED=95=AD=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 쿼리 주최를 변경함 --- .../repository/GroupCapsuleQueryRepository.java | 7 +++++++ .../MemberFriendQueryRepository.java | 2 ++ .../MemberFriendQueryRepositoryImpl.java | 8 ++++++++ .../group/repository/GroupQueryRepository.java | 8 ++------ .../repository/GroupQueryRepositoryImpl.java | 17 ----------------- .../group/service/query/GroupQueryService.java | 8 ++++++-- 6 files changed, 25 insertions(+), 25 deletions(-) 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 b305e80ac..3efe95754 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 @@ -176,4 +176,11 @@ public boolean findGroupCapsuleExistByGroupId(Long groupId) { return count != null; } + + public Long findGroupCapsuleCount(final Long groupId) { + return jpaQueryFactory.select(capsule.count()) + .from(capsule) + .where(capsule.group.id.eq(groupId)) + .fetchOne(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java index 48471570c..8e8c32667 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java @@ -38,4 +38,6 @@ Optional findFriendsByTag( Slice findFriendsBeforeGroupInvite( final FriendBeforeGroupInviteRequest request); + + List findFriendIds(final List groupMemberIds, final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 5804b2d36..3943520e4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -193,4 +193,12 @@ public List findFriendIdsByOwnerId(Long memberId) { .where(memberFriend.owner.id.eq(memberId)) .fetch(); } + + public List findFriendIds(final List groupMemberIds, final Long memberId) { + return jpaQueryFactory.select(memberFriend.friend.id) + .from(memberFriend) + .where( + memberFriend.friend.id.in(groupMemberIds).and(memberFriend.owner.id.eq(memberId))) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index c3cbd537e..2760a269e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -1,7 +1,6 @@ package site.timecapsulearchive.core.domain.group.repository; import java.time.ZonedDateTime; -import java.util.List; import java.util.Optional; import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; @@ -16,11 +15,8 @@ Slice findGroupsSlice( final ZonedDateTime createdAt ); - Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId); - - Long findGroupCapsuleCount(final Long groupId); + Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, + final Long memberId); Boolean findGroupEditPermission(final Long groupId, final Long memberId); - - List findFriendIds(final List groupMemberIds, final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index 22fa95b4c..ef08d827d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -2,8 +2,6 @@ import static com.querydsl.core.group.GroupBy.groupBy; import static com.querydsl.core.group.GroupBy.list; -import static site.timecapsulearchive.core.domain.capsule.entity.QCapsule.capsule; -import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; @@ -92,25 +90,10 @@ public Optional findGroupDetailByGroupIdAndMemberId(final Long g ); } - public Long findGroupCapsuleCount(final Long groupId) { - return jpaQueryFactory.select(capsule.count()) - .from(capsule) - .where(capsule.group.id.eq(groupId)) - .fetchOne(); - } - public Boolean findGroupEditPermission(final Long groupId, final Long memberId) { return jpaQueryFactory.select(memberGroup.isOwner) .from(memberGroup) .where(memberGroup.group.id.eq(groupId).and(memberGroup.member.id.eq(memberId))) .fetchOne(); } - - public List findFriendIds(final List groupMemberIds, final Long memberId) { - return jpaQueryFactory.select(memberFriend.friend.id) - .from(memberFriend) - .where( - memberFriend.friend.id.in(groupMemberIds).and(memberFriend.owner.id.eq(memberId))) - .fetch(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index 56571ad6f..309d03ed8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -6,6 +6,8 @@ import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; @@ -20,6 +22,8 @@ public class GroupQueryService { private final GroupRepository groupRepository; + private final GroupCapsuleQueryRepository groupCapsuleQueryRepository; + private final MemberFriendRepository memberFriendRepository; public Group findGroupById(final Long groupId) { return groupRepository.findGroupById(groupId) @@ -38,13 +42,13 @@ public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final L final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupIdAndMemberId(groupId, memberId).orElseThrow(GroupNotFoundException::new); - final Long groupCapsuleCount = groupRepository.findGroupCapsuleCount(groupId); + final Long groupCapsuleCount = groupCapsuleQueryRepository.findGroupCapsuleCount(groupId); final Boolean canGroupEdit = groupRepository.findGroupEditPermission(groupId, memberId); final List groupMemberIds = groupDetailDto.members().stream() .map(GroupMemberDto::memberId) .toList(); - final List friendIds = groupRepository.findFriendIds(groupMemberIds, memberId); + final List friendIds = memberFriendRepository.findFriendIds(groupMemberIds, memberId); return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, canGroupEdit, friendIds); } From 6df2b5ca78ca4e1883a458930d286969c132d435 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 27 May 2024 15:28:07 +0900 Subject: [PATCH 060/195] =?UTF-8?q?chore=20:=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EC=9E=90=EB=B0=94=20=ED=98=95=EC=8B=9D=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/service/query/GroupQueryService.java | 3 ++- .../groupInviteRepository/GroupInviteQueryRepositoryImpl.java | 4 ++-- .../member_group/service/MemberGroupCommandService.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index 309d03ed8..016377994 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -39,7 +39,8 @@ public Slice findGroupsSlice( } public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { - final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupIdAndMemberId(groupId, + final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupIdAndMemberId( + groupId, memberId).orElseThrow(GroupNotFoundException::new); final Long groupCapsuleCount = groupCapsuleQueryRepository.findGroupCapsuleCount(groupId); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index b31ac6426..2936216ef 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -4,7 +4,6 @@ import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QGroupInvite.groupInvite; -import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -30,7 +29,8 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepositor private final JdbcTemplate jdbcTemplate; private final JPAQueryFactory jpaQueryFactory; - public void bulkSave(final Long groupOwnerId, final Long groupId, final List groupMemberIds) { + public void bulkSave(final Long groupOwnerId, final Long groupId, + final List groupMemberIds) { jdbcTemplate.batchUpdate( """ INSERT INTO group_invite ( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index c902bb6d5..7596c951d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -17,9 +17,9 @@ import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; -import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; From 8b3b09f46874447247ea8047f9345eb50cbc00d2 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Tue, 28 May 2024 14:33:29 +0900 Subject: [PATCH 061/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=EC=82=AC=ED=95=AD=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=EB=B3=B8=EC=9D=B8=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=ED=95=98=EA=B3=A0=20=EA=B7=B8=EB=A3=B9=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=EA=B0=80=20=EC=95=84=EB=AC=B4=EB=8F=84=20=EC=97=86?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/group/data/dto/GroupDetailDto.java | 10 +++++++++ .../repository/GroupQueryRepositoryImpl.java | 22 ++++++++++++++++--- .../service/query/GroupQueryService.java | 3 +-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java index e80c619f6..ed8cff73e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java @@ -11,4 +11,14 @@ public record GroupDetailDto( List members ) { + public static GroupDetailDto as( + String groupName, + String groupDescription, + String groupProfileUrl, + ZonedDateTime createdAt, + List members + ) { + return new GroupDetailDto(groupName, groupDescription, groupProfileUrl, createdAt, members); + } + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index ef08d827d..6d42c08a2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -10,6 +10,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import java.time.ZonedDateTime; import java.util.List; +import java.util.Objects; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -59,12 +60,11 @@ public Slice findGroupsSlice( public Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId) { - return Optional.ofNullable( + GroupDetailDto groupDetailDtoIncludeMe = jpaQueryFactory .selectFrom(group) .join(memberGroup).on(memberGroup.group.id.eq(groupId)) .join(member).on(member.id.eq(memberGroup.member.id)) - .where(member.id.ne(memberId)) .transform( groupBy(group.id).as( Projections.constructor( @@ -86,7 +86,23 @@ public Optional findGroupDetailByGroupIdAndMemberId(final Long g ) ) ) - .get(groupId) + .get(groupId); + + if (Objects.isNull(groupDetailDtoIncludeMe)) { + return Optional.empty(); + } + + List groupMemberDtosExcludeMe = groupDetailDtoIncludeMe.members().stream() + .filter(memberDto -> !memberDto.memberId().equals(memberId)) + .toList(); + + return Optional.of( + GroupDetailDto.as( + groupDetailDtoIncludeMe.groupName(), + groupDetailDtoIncludeMe.groupDescription(), + groupDetailDtoIncludeMe.groupProfileUrl(), + groupDetailDtoIncludeMe.createdAt(), + groupMemberDtosExcludeMe) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index 016377994..c971191f7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -40,8 +40,7 @@ public Slice findGroupsSlice( public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { final GroupDetailDto groupDetailDto = groupRepository.findGroupDetailByGroupIdAndMemberId( - groupId, - memberId).orElseThrow(GroupNotFoundException::new); + groupId, memberId).orElseThrow(GroupNotFoundException::new); final Long groupCapsuleCount = groupCapsuleQueryRepository.findGroupCapsuleCount(groupId); final Boolean canGroupEdit = groupRepository.findGroupEditPermission(groupId, memberId); From 5d10046c7cc36c591c37308f31715ba93da993e6 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 11:37:57 +0900 Subject: [PATCH 062/195] =?UTF-8?q?fix=20:=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자가 인식하고 타이핑하기 더 쉬운 태그로 변경 - FullText Index를 설정해 잦은 태그 검색에도 대응 --- backend/core/build.gradle | 3 - .../service/MessageVerificationService.java | 10 ++++ .../friend/api/query/FriendQueryApi.java | 8 ++- .../api/query/FriendQueryApiController.java | 6 +- .../MemberFriendQueryRepositoryImpl.java | 38 +++++++++--- .../service/query/FriendQueryService.java | 11 +--- .../member/data/dto/SignUpRequestDto.java | 5 +- .../member/data/mapper/MemberMapper.java | 10 ++-- .../core/domain/member/entity/Member.java | 4 ++ .../domain/member/entity/MemberTemporary.java | 5 ++ .../repository/MemberQueryRepository.java | 2 + .../repository/MemberQueryRepositoryImpl.java | 19 ++++++ .../domain/member/service/MemberService.java | 16 +++-- .../{ => repository}/JpaAuditingConfig.java | 2 +- .../repository/MySQLFunctionContributor.java | 19 ++++++ .../{ => repository}/QueryDSLConfig.java | 2 +- .../TransactionTemplateConfig.java | 2 +- .../service/CustomOAuth2UserService.java | 9 +++ .../core/global/util/TagGenerator.java | 59 +++++++++++++++++++ ...g.hibernate.boot.model.FunctionContributor | 1 + .../V28__member_tag_fulltext_index.sql | 1 + 21 files changed, 196 insertions(+), 36 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/global/config/{ => repository}/JpaAuditingConfig.java (90%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/MySQLFunctionContributor.java rename backend/core/src/main/java/site/timecapsulearchive/core/global/config/{ => repository}/QueryDSLConfig.java (87%) rename backend/core/src/main/java/site/timecapsulearchive/core/global/config/{ => repository}/TransactionTemplateConfig.java (91%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java create mode 100644 backend/core/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor create mode 100644 backend/core/src/main/resources/db/migration/V28__member_tag_fulltext_index.sql diff --git a/backend/core/build.gradle b/backend/core/build.gradle index f35c4a4fc..7520ee105 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -74,9 +74,6 @@ dependencies { //aop implementation 'org.springframework.boot:spring-boot-starter-aop' - //jnanoid - implementation 'com.aventrix.jnanoid:jnanoid:2.0.0' - compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java index 69cd6f204..54e7a1afa 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java @@ -3,6 +3,7 @@ import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; @@ -20,6 +21,7 @@ import site.timecapsulearchive.core.infra.sms.data.response.SmsApiResponse; import site.timecapsulearchive.core.infra.sms.manager.SmsApiManager; +@Slf4j @Service @Transactional @RequiredArgsConstructor @@ -109,6 +111,14 @@ private Long updateToVerifiedMember(final Long memberId, final byte[] plain) { memberTemporaryRepository.delete(memberTemporary); + boolean isDuplicateTag = memberRepository.checkTagDuplication(memberTemporary.getTag()); + if (isDuplicateTag) { + log.warn("member tag duplicate - email:{}, tag:{}", memberTemporary.getEmail(), + memberTemporary.getTag()); + memberTemporary.updateTagLowerCaseSocialType(); + log.warn("member tag update - tag: {}", memberTemporary.getTag()); + } + final Member verifiedMember = memberTemporary.toMember(hashEncryptionManager.encrypt(plain), aesEncryptionManager.encryptWithPrefixIV(plain)); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java index 3813489e0..0cd05496a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java @@ -104,8 +104,12 @@ ResponseEntity> searchMembersByPhones( ); @Operation( - summary = "찬구 검색", - description = "친구의 tag로 친구 검색을 한다.", + summary = "친구 검색", + description = """ + 친구의 tag로 친구 검색을 한다. +
+ 태그가 일치하면 일치하는 태그를 가진 사용자를 일치하지 않으면 가장 비슷한 태그를 가진 사용자를 반환한다. + """, security = {@SecurityRequirement(name = "user_token")}, tags = {"friend"} ) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java index 4e990d0ae..f9856fb3b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; +import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; @@ -127,10 +128,13 @@ public ResponseEntity> searchFriendByTag @AuthenticationPrincipal Long memberId, @RequestParam(value = "friend_tag") final String tag ) { + SearchFriendSummaryDtoByTag searchResult = friendQueryService.searchFriend( + memberId, tag); + return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - friendQueryService.searchFriend(memberId, tag) + searchResult.toResponse() ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 3943520e4..e58fdc147 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -6,7 +6,11 @@ import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberTemplate; import com.querydsl.jpa.impl.JPAQueryFactory; import java.time.ZonedDateTime; import java.util.List; @@ -27,6 +31,11 @@ @RequiredArgsConstructor public class MemberFriendQueryRepositoryImpl implements MemberFriendQueryRepository { + private static final double MATCH_THRESHOLD = 0; + private static final String MATCH_AGAINST_FUNCTION = "function('match_against', {0}, {1})"; + private static final String FRIEND_INVITE_TO_ME_PATH = "friendInviteToMe"; + private static final String FRIEND_INVITE_TO_FRIEND_PATH = "friendInviteToFriend"; + private final JPAQueryFactory jpaQueryFactory; public Slice findFriendsSlice( @@ -121,8 +130,8 @@ public List findFriendsByPhone( final Long memberId, final List hashes ) { - final QFriendInvite friendInviteToFriend = new QFriendInvite("friendInviteToFriend"); - final QFriendInvite friendInviteToMe = new QFriendInvite("friendInviteToMe"); + final QFriendInvite friendInviteToFriend = new QFriendInvite(FRIEND_INVITE_TO_FRIEND_PATH); + final QFriendInvite friendInviteToMe = new QFriendInvite(FRIEND_INVITE_TO_ME_PATH); return jpaQueryFactory .select( @@ -157,8 +166,21 @@ public Optional findFriendsByTag( final Long memberId, final String tag ) { - final QFriendInvite friendInviteToFriend = new QFriendInvite("friendInviteToFriend"); - final QFriendInvite friendInviteToMe = new QFriendInvite("friendInviteToMe"); + if (tag != null && tag.isBlank()) { + return Optional.empty(); + } + + final QFriendInvite friendInviteToFriend = new QFriendInvite(FRIEND_INVITE_TO_FRIEND_PATH); + final QFriendInvite friendInviteToMe = new QFriendInvite(FRIEND_INVITE_TO_ME_PATH); + + NumberTemplate tagMatchTemplate = Expressions.numberTemplate(Double.class, + MATCH_AGAINST_FUNCTION, + member.tag, + tag); + + OrderSpecifier tagEqCaseDesc = new CaseBuilder().when(member.tag.eq(tag)) + .then(Boolean.TRUE) + .otherwise(Boolean.FALSE).desc(); return Optional.ofNullable(jpaQueryFactory .select( @@ -179,9 +201,11 @@ public Optional findFriendsByTag( .on(friendInviteToFriend.friend.id.eq(member.id) .and(friendInviteToFriend.owner.id.eq(memberId))) .leftJoin(friendInviteToMe) - .on(friendInviteToMe.friend.id.eq(memberId) - .and(friendInviteToMe.owner.id.eq(member.id))) - .where(member.tag.eq(tag)) + .on(friendInviteToMe.owner.id.eq(member.id) + .and(friendInviteToMe.friend.id.eq(memberId))) + .where(tagMatchTemplate.gt(MATCH_THRESHOLD)) + .orderBy(tagEqCaseDesc, tagMatchTemplate.desc()) + .limit(1L) .fetchOne() ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index d13efda75..d34230f0a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -10,7 +10,6 @@ import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; -import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @@ -53,12 +52,8 @@ public List findFriendsByPhone( return memberFriendRepository.findFriendsByPhone(memberId, hashes); } - public SearchTagFriendSummaryResponse searchFriend(final Long memberId, final String tag) { - final SearchFriendSummaryDtoByTag friendSummaryDto = memberFriendRepository - .findFriendsByTag(memberId, tag).orElseThrow(FriendNotFoundException::new); - - return friendSummaryDto.toResponse(); + public SearchFriendSummaryDtoByTag searchFriend(final Long memberId, final String tag) { + return memberFriendRepository.findFriendsByTag(memberId, tag) + .orElseThrow(FriendNotFoundException::new); } - - } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java index 4ec2aa7c3..bf7969ca9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java @@ -1,6 +1,5 @@ package site.timecapsulearchive.core.domain.member.data.dto; -import com.aventrix.jnanoid.jnanoid.NanoIdUtils; import site.timecapsulearchive.core.domain.member.entity.MemberTemporary; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil; @@ -12,14 +11,14 @@ public record SignUpRequestDto( SocialType socialType ) { - public MemberTemporary toMemberTemporary() { + public MemberTemporary toMemberTemporary(final String tag) { return MemberTemporary.builder() .authId(authId) .nickname(MakeRandomNickNameUtil.makeRandomNickName()) .email(email) .profileUrl(profileUrl) .socialType(socialType) - .tag(NanoIdUtils.randomNanoId()) + .tag(tag) .build(); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java index d25037621..b24b883ae 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java @@ -1,6 +1,5 @@ package site.timecapsulearchive.core.domain.member.data.mapper; -import com.aventrix.jnanoid.jnanoid.NanoIdUtils; import java.time.ZoneId; import java.util.List; import java.util.UUID; @@ -12,6 +11,7 @@ import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.global.security.oauth.dto.OAuth2UserInfo; +import site.timecapsulearchive.core.global.util.TagGenerator; import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil; import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; @@ -33,7 +33,7 @@ public Member OAuthToEntity( .email(oAuth2UserInfo.getEmail()) .profileUrl(oAuth2UserInfo.getImageUrl()) .socialType(socialType) - .tag(NanoIdUtils.randomNanoId()) + .tag(TagGenerator.generate(oAuth2UserInfo.getEmail(), socialType)) .build(); } @@ -64,13 +64,15 @@ public MemberNotificationSliceResponse notificationSliceToResponse( } public Member createMemberWithEmail(String email, String password) { + SocialType socialType = SocialType.EMAIL; + return Member.builder() .email(email) .password(password) .authId(String.valueOf(UUID.randomUUID())) .profileUrl("") - .socialType(SocialType.EMAIL) - .tag(NanoIdUtils.randomNanoId()) + .socialType(socialType) + .tag(TagGenerator.generate(email, socialType)) .build(); } } \ No newline at end of file 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 31cdfff02..e94e51515 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 @@ -23,6 +23,7 @@ import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.global.entity.BaseEntity; import site.timecapsulearchive.core.global.util.NullCheck; +import site.timecapsulearchive.core.global.util.TagGenerator; @Entity @Getter @@ -107,4 +108,7 @@ private Member(String profileUrl, String nickname, SocialType socialType, String this.phone_hash = phone_hash; } + public void updateTagLowerCaseSocialType() { + this.tag = TagGenerator.lowercase(email, socialType); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java index 7ecb0e197..741faa054 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java @@ -15,6 +15,7 @@ import lombok.NoArgsConstructor; import site.timecapsulearchive.core.global.entity.BaseEntity; import site.timecapsulearchive.core.global.util.NullCheck; +import site.timecapsulearchive.core.global.util.TagGenerator; @Entity @Getter @@ -73,4 +74,8 @@ public Member toMember(final byte[] phone_hash, final byte[] phone) { .phone(phone) .build(); } + + public void updateTagLowerCaseSocialType() { + this.tag = TagGenerator.lowercase(email, socialType); + } } 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 469bb89a5..13827ea1f 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 @@ -44,4 +44,6 @@ List findMemberNotificationDtos( Optional findIsAlarmByMemberId(final Long memberId); List findMemberIdsByIds(List ids); + + boolean checkTagDuplication(String tag); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java index 27453148a..0afced1fb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java @@ -30,6 +30,7 @@ public class MemberQueryRepositoryImpl implements MemberQueryRepository { private final JPAQueryFactory query; + @Override public Boolean findIsVerifiedByAuthIdAndSocialType( final String authId, final SocialType socialType @@ -40,6 +41,7 @@ public Boolean findIsVerifiedByAuthIdAndSocialType( .fetchOne(); } + @Override public Optional findVerifiedCheckDtoByAuthIdAndSocialType( final String authId, final SocialType socialType @@ -59,6 +61,7 @@ public Optional findVerifiedCheckDtoByAuthIdAndSocialType( ); } + @Override public Optional findMemberDetailResponseDtoById(final Long memberId) { return Optional.ofNullable( query @@ -85,6 +88,7 @@ private NumberExpression countDistinct(final NumberExpression expres return Expressions.numberTemplate(Long.class, "COUNT(DISTINCT {0})", expression); } + @Override public Slice findNotificationSliceByMemberId( final Long memberId, final int size, @@ -101,6 +105,7 @@ public Slice findNotificationSliceByMemberId( return new SliceImpl<>(notifications, Pageable.ofSize(size), hasNext); } + @Override public List findMemberNotificationDtos( final Long memberId, final int size, @@ -126,6 +131,7 @@ public List findMemberNotificationDtos( .fetch(); } + @Override public Optional findEmailVerifiedCheckDtoByEmail( final String email ) { @@ -146,6 +152,7 @@ public Optional findEmailVerifiedCheckDtoByEmail( ); } + @Override public Boolean checkEmailDuplication(final String email) { final Integer count = query.selectOne() .from(member) @@ -155,6 +162,7 @@ public Boolean checkEmailDuplication(final String email) { return count != null; } + @Override public Optional findIsAlarmByMemberId(final Long memberId) { return Optional.ofNullable( query.select(member.notificationEnabled) @@ -164,6 +172,7 @@ public Optional findIsAlarmByMemberId(final Long memberId) { ); } + @Override public List findMemberIdsByIds(List ids) { return query .select(member.id) @@ -171,4 +180,14 @@ public List findMemberIdsByIds(List ids) { .where(member.id.in(ids)) .fetch(); } + + @Override + public boolean checkTagDuplication(String tag) { + final Integer count = query.selectOne() + .from(member) + .where(member.tag.eq(tag)) + .fetchFirst(); + + return count != null; + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 7799528d0..023ac2d41 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -27,6 +27,7 @@ import site.timecapsulearchive.core.domain.member.exception.NotVerifiedMemberException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; +import site.timecapsulearchive.core.global.util.TagGenerator; @Slf4j @Service @@ -42,7 +43,8 @@ public class MemberService { @Transactional public Long createMember(final SignUpRequestDto dto) { - final MemberTemporary member = dto.toMemberTemporary(); + final String tag = TagGenerator.generate(dto.email(), dto.socialType()); + final MemberTemporary member = dto.toMemberTemporary(tag); final MemberTemporary savedMember = memberTemporaryRepository.save(member); @@ -165,6 +167,14 @@ public Long createMemberWithEmailAndPassword(final String email, final String pa final String encodedPassword = passwordEncoder.encode(password); final Member member = memberMapper.createMemberWithEmail(email, encodedPassword); + boolean isDuplicateTag = memberRepository.checkTagDuplication(member.getTag()); + if (isDuplicateTag) { + log.warn("member tag duplicate - email:{}, tag:{}", member.getEmail(), + member.getTag()); + member.updateTagLowerCaseSocialType(); + log.warn("member tag update - tag: {}", member.getTag()); + } + final Member savedMember = memberRepository.save(member); return savedMember.getId(); @@ -214,8 +224,4 @@ public Member findMemberById(final Long memberId) { return memberRepository.findMemberById(memberId) .orElseThrow(MemberNotFoundException::new); } - - public List findMemberIdsByIds(List ids) { - return memberRepository.findMemberIdsByIds(ids); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/JpaAuditingConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/JpaAuditingConfig.java similarity index 90% rename from backend/core/src/main/java/site/timecapsulearchive/core/global/config/JpaAuditingConfig.java rename to backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/JpaAuditingConfig.java index 2d7527fbe..7decacfb8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/JpaAuditingConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/JpaAuditingConfig.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.global.config; +package site.timecapsulearchive.core.global.config.repository; import java.time.ZoneId; import java.time.ZonedDateTime; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/MySQLFunctionContributor.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/MySQLFunctionContributor.java new file mode 100644 index 000000000..c441b50f5 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/MySQLFunctionContributor.java @@ -0,0 +1,19 @@ +package site.timecapsulearchive.core.global.config.repository; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.boot.model.FunctionContributor; +import org.hibernate.type.StandardBasicTypes; + +public class MySQLFunctionContributor implements FunctionContributor { + + private static final String FUNCTION_NAME = "match_against"; + private static final String FUNCTION_PATTERN = "match (?1) against (?2 in boolean mode)"; + + @Override + public void contributeFunctions(final FunctionContributions functionContributions) { + functionContributions.getFunctionRegistry() + .registerPattern(FUNCTION_NAME, FUNCTION_PATTERN, + functionContributions.getTypeConfiguration().getBasicTypeRegistry() + .resolve(StandardBasicTypes.DOUBLE)); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/QueryDSLConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/QueryDSLConfig.java similarity index 87% rename from backend/core/src/main/java/site/timecapsulearchive/core/global/config/QueryDSLConfig.java rename to backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/QueryDSLConfig.java index 094973e71..6e292a16e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/QueryDSLConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/QueryDSLConfig.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.global.config; +package site.timecapsulearchive.core.global.config.repository; import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQueryFactory; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/TransactionTemplateConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/TransactionTemplateConfig.java similarity index 91% rename from backend/core/src/main/java/site/timecapsulearchive/core/global/config/TransactionTemplateConfig.java rename to backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/TransactionTemplateConfig.java index 8927ee60f..5c249962f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/TransactionTemplateConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/repository/TransactionTemplateConfig.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.global.config; +package site.timecapsulearchive.core.global.config.repository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java index cddd7fa14..4030aca79 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java @@ -76,6 +76,15 @@ private Member saveMember(final SocialType socialType, final OAuthAttributes att socialType, attributes.getOauth2UserInfo() ); + + boolean isDuplicateTag = memberRepository.checkTagDuplication(createMember.getTag()); + if (isDuplicateTag) { + log.warn("member tag duplicate - email:{}, tag:{}", createMember.getEmail(), + createMember.getTag()); + createMember.updateTagLowerCaseSocialType(); + log.warn("member tag update - tag: {}", createMember.getTag()); + } + return memberRepository.save(createMember); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java new file mode 100644 index 000000000..e1dab1ae9 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java @@ -0,0 +1,59 @@ +package site.timecapsulearchive.core.global.util; + +import java.security.SecureRandom; +import java.util.stream.Collectors; +import site.timecapsulearchive.core.domain.member.entity.SocialType; + +public final class TagGenerator { + + private static final int SIZE = 6; + private static final int BOUND = 10; + private static final String EMAIL_DELIMITER = "@"; + private static final String HYPHEN = "-"; + private static final SecureRandom secureRandom = new SecureRandom(); + + /** + * 이메일과 소셜 타입(대문자)으로 태그를 생성한다. + * + * @param email 이메일 + * @param socialType 소셜 타입 + * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> {@code "test1234-123456GG"} + */ + public static String generate(final String email, final SocialType socialType) { + final String randomInts = generateRandomInts(); + + final String[] splitEmail = email.split(EMAIL_DELIMITER); + + return splitEmail[0] + + HYPHEN + + randomInts + + Character.toUpperCase(splitEmail[1].charAt(0)) + + socialType.name().charAt(0); + } + + private static String generateRandomInts() { + return secureRandom + .ints(SIZE, 0, BOUND) + .mapToObj(String::valueOf) + .collect(Collectors.joining()); + } + + /** + * 이메일과 소셜 타입(소문자)으로 태그를 생성한다. + * + * @param email 이메일 + * @param socialType 소셜 타입 + * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> {@code "test1234-123456gg"} + */ + public static String lowercase(final String email, final SocialType socialType) { + final String randomInts = generateRandomInts(); + + final String[] splitEmail = email.split(EMAIL_DELIMITER); + + return splitEmail[0] + + HYPHEN + + randomInts + + Character.toLowerCase(splitEmail[1].charAt(0)) + + Character.toLowerCase(socialType.name().charAt(0)); + } +} diff --git a/backend/core/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor b/backend/core/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor new file mode 100644 index 000000000..adc3ea649 --- /dev/null +++ b/backend/core/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor @@ -0,0 +1 @@ +site.timecapsulearchive.core.global.config.repository.MySQLFunctionContributor \ No newline at end of file diff --git a/backend/core/src/main/resources/db/migration/V28__member_tag_fulltext_index.sql b/backend/core/src/main/resources/db/migration/V28__member_tag_fulltext_index.sql new file mode 100644 index 000000000..f4e5104a8 --- /dev/null +++ b/backend/core/src/main/resources/db/migration/V28__member_tag_fulltext_index.sql @@ -0,0 +1 @@ +alter table member add fulltext index fx_tag(tag) with parser ngram; \ No newline at end of file From 7a767b09598a52731aef478eeed8f0e14b155f10 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 11:38:11 +0900 Subject: [PATCH 063/195] =?UTF-8?q?test=20:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/common/RepositoryTest.java | 4 +- .../MemberFriendQueryRepositoryTest.java | 107 ++++++++++++++---- .../service/FriendQueryServiceTest.java | 2 +- .../db/migration/V999__member_tag_insert.sql | 18 +++ 4 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 backend/core/src/test/resources/db/migration/V999__member_tag_insert.sql diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/RepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/RepositoryTest.java index c0430087e..18ada1717 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/RepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/RepositoryTest.java @@ -7,8 +7,8 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; -import site.timecapsulearchive.core.global.config.JpaAuditingConfig; -import site.timecapsulearchive.core.global.config.QueryDSLConfig; +import site.timecapsulearchive.core.global.config.repository.JpaAuditingConfig; +import site.timecapsulearchive.core.global.config.repository.QueryDSLConfig; @Import(value = {JpaAuditingConfig.class, QueryDSLConfig.class}) @DataJpaTest diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index 7831111fe..6e9105075 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.friend.repository; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -11,6 +12,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -19,7 +21,6 @@ import org.springframework.data.domain.Slice; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; -import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.common.RepositoryTest; import site.timecapsulearchive.core.common.fixture.domain.FriendInviteFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; @@ -41,19 +42,19 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private static final Long FRIEND_ID_TO_INVITE_OWNER = 11L; private static final Long NOT_FRIEND_MEMBER_START_ID = 12L; + private final MemberFriendQueryRepository memberFriendQueryRepository; + private final List hashedNotMemberPhones = new ArrayList<>(); private final List hashedFriendPhones = new ArrayList<>(); private final List hashedNotFriendPhones = new ArrayList<>(); - private final MemberFriendQueryRepository memberFriendQueryRepository; private Long ownerId; private Long friendId; - MemberFriendQueryRepositoryTest(EntityManager entityManager) { + MemberFriendQueryRepositoryTest(@Autowired EntityManager entityManager) { this.memberFriendQueryRepository = new MemberFriendQueryRepositoryImpl( new JPAQueryFactory(entityManager)); } - @Transactional @BeforeEach void setup(@Autowired EntityManager entityManager) { Member owner = MemberFixture.member(0); @@ -78,19 +79,18 @@ void setup(@Autowired EntityManager entityManager) { Member inviteFriendToOwner = MemberFixture.member(FRIEND_ID_TO_INVITE_OWNER.intValue()); entityManager.persist(inviteFriendToOwner); - FriendInvite friendInvite = FriendInviteFixture.friendInvite(inviteFriendToOwner, owner); + FriendInvite friendInvite = FriendInviteFixture.friendInvite(inviteFriendToOwner, + owner); entityManager.persist(friendInvite); //owner와 친구가 아닌 멤버 데이터 - List notFriendMembers = MemberFixture.members(NOT_FRIEND_MEMBER_START_ID.intValue(), + List notFriendMembers = MemberFixture.members( + NOT_FRIEND_MEMBER_START_ID.intValue(), MAX_COUNT); for (Member notFriend : notFriendMembers) { entityManager.persist(notFriend); hashedNotFriendPhones.add(notFriend.getPhone_hash()); } - - //회원이 아닌 휴대전화번호 데이터 - hashedNotMemberPhones.addAll(MemberFixture.getPhoneBytesList(23, MAX_COUNT)); } @ParameterizedTest @@ -274,31 +274,96 @@ void setup(@Autowired EntityManager entityManager) { assertThat(friends).isEmpty(); } - @ParameterizedTest - @ValueSource(ints = {3, 6, 10, 5}) - void 사용자가_친구_태그로_친구관계를_조회하면_친구인_경우_True를_반환한다(int friendId) { + @Test + void 태그로_검색하면_가장_비슷한_태그를_가진_사용자_한_명만_반환한다() { + //given + String tag = "test_tag"; + + //when + Optional dto = memberFriendQueryRepository.findFriendsByTag( + ownerId, tag); + + //then + assertThat(dto).isPresent(); + } + + @Test + void 일치하는_태그로_검색하면_일치하는_태그를_가진_사용자_한_명만만_나온다() { + //given + String tag = "test_tag_9999"; + + //when + SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( + ownerId, tag).orElseThrow(); + + //then + assertThat(dto.nickname()).isEqualTo("test_nickname_9999"); + } + + @Test + void 일치하지_않는_태그로_검색하면_결과가_나오지_않는다() { + //given + String tag = "trash"; + + //when + //then + assertThatThrownBy(() -> memberFriendQueryRepository.findFriendsByTag( + ownerId, tag).orElseThrow()); + } + + @Test + void 사용자가_친구_태그로_친구관계를_조회하면_친구인_경우_True를_반환한다() { //given + String tag = "test_tag_9999"; + Long ownerId = 10000L; + + //when SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( - ownerId, friendId + "testTag").orElseThrow(); + ownerId, tag).orElseThrow(); + + //then + assertThat(dto.isFriend()).isTrue(); + } + + @Test + void 사용자가_친구_태그로_친구관계를_조회하면_친구가_아닌_경우_False를_반환한다() { + //given + String tag = "test_tag_9999"; + Long ownerId = 10003L; //when - Boolean isFriend = dto.isFriend(); + SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( + ownerId, tag).orElseThrow(); //then - assertThat(isFriend).isTrue(); + assertThat(dto.isFriend()).isFalse(); } - @ParameterizedTest - @ValueSource(ints = {12, 15, 19, 21}) - void 사용자가_친구_태그로_친구관계를_조회하면_친구가_아닌_경우_False를_반환한다(int friendId) { + @Test + void 사용자가_친구_태그로_친구초대관계를_조회하면_친구_초대한_경우_True를_반환한다() { //given + String tag = "test_tag_9999"; + Long ownerId = 10001L; + + //when SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( - ownerId, friendId + "testTag").orElseThrow(); + ownerId, tag).orElseThrow(); + + //then + assertThat(dto.isFriend()).isTrue(); + } + + @Test + void 사용자가_친구_태그로_친구초대관계를_조회하면_친구_초대하지_않은_경우_False를_반환한다() { + //given + String tag = "test_tag_9999"; + Long ownerId = 10003L; //when - Boolean isFriend = dto.isFriend(); + SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( + ownerId, tag).orElseThrow(); //then - assertThat(isFriend).isFalse(); + assertThat(dto.isFriend()).isFalse(); } } \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java index 8eea61d4b..6c940d657 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java @@ -77,7 +77,7 @@ class FriendQueryServiceTest { .willReturn(summaryDtoByTag); //when - SearchTagFriendSummaryResponse actualResponse = friendQueryService.searchFriend( + SearchFriendSummaryDtoByTag actualResponse = friendQueryService.searchFriend( memberId, tag); //then diff --git a/backend/core/src/test/resources/db/migration/V999__member_tag_insert.sql b/backend/core/src/test/resources/db/migration/V999__member_tag_insert.sql new file mode 100644 index 000000000..5bd0618bb --- /dev/null +++ b/backend/core/src/test/resources/db/migration/V999__member_tag_insert.sql @@ -0,0 +1,18 @@ +insert into member (is_verified, notification_enabled, created_at, member_id, updated_at, phone, nickname, social_type, email, fcm_token, profile_url, auth_id, phone_hash, tag, password) +values (true, false, now(), 9999, now(), null, 'test_nickname_9999', 'KAKAO', 'test_email_9999@gmail.com', null, 'http://test_profile_url_9999.com', 'test_auth_id_9999', null, 'test_tag_9999', null), + (true, false, now(), 10000, now(), null, 'test_nickname_10000', 'KAKAO', 'test_email_10000@gmail.co', null, 'http://test_profile_url_10000.com', 'test_auth_id_10000', null, 'test_tag_10000', null), + (true, false, now(), 10001, now(), null, 'test_nickname_10001', 'KAKAO', 'test_email_10001@gmail.co', null, 'http://test_profile_url_10001.com', 'test_auth_id_10001', null, 'test_tag_10001', null), + (true, false, now(), 10002, now(), null, 'test_nickname_10002', 'KAKAO', 'test_email_10002@gmail.co', null, 'http://test_profile_url_10002.com', 'test_auth_id_10002', null, 'test_tag_10002', null), + (true, false, now(), 10003, now(), null, 'test_nickname_10003', 'KAKAO', 'test_email_10003@gmail.co', null, 'http://test_profile_url_10003.com', 'test_auth_id_10003', null, 'test_tag_10003', null); + +insert into member_friend(member_friend_id, owner_id, friend_id, created_at, updated_at) +values (1, 10000, 9999, now(), now()), + (2, 9999, 10000, now(), now()), + (3, 9999, 10001, now(), now()), + (4, 10001, 9999, now(), now()); + +insert into friend_invite(friend_invite_id, owner_id, friend_id, created_at, updated_at) +values (1, 10002, 9999, now(), now()), + (2, 9999, 10002, now(), now()), + (3, 9999, 10003, now(), now()), + (4, 10003, 9999, now(), now()); \ No newline at end of file From 61b727fe2568bc5cb3bb0bc0157d1b82c9a3e2ab Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 12:55:26 +0900 Subject: [PATCH 064/195] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/group/data/dto/GroupDetailDto.java | 4 ++- .../group/data/dto/GroupDetailTotalDto.java | 3 +- .../repository/GroupQueryRepository.java | 2 -- .../repository/GroupQueryRepositoryImpl.java | 36 ++++++++++++------- .../service/query/GroupQueryService.java | 3 +- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java index ed8cff73e..d7852ebdf 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java @@ -8,6 +8,7 @@ public record GroupDetailDto( String groupDescription, String groupProfileUrl, ZonedDateTime createdAt, + Boolean isOwner, List members ) { @@ -16,9 +17,10 @@ public static GroupDetailDto as( String groupDescription, String groupProfileUrl, ZonedDateTime createdAt, + Boolean isOwner, List members ) { - return new GroupDetailDto(groupName, groupDescription, groupProfileUrl, createdAt, members); + return new GroupDetailDto(groupName, groupDescription, groupProfileUrl, createdAt, isOwner, members); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java index 5a83ba17e..55209b7c3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java @@ -19,7 +19,6 @@ public record GroupDetailTotalDto( public static GroupDetailTotalDto as( final GroupDetailDto groupDetailDto, final Long groupCapsuleTotalCount, - final Boolean canGroupEdit, final List friendIds ) { final List membersWithRelation = groupDetailDto.members() @@ -33,7 +32,7 @@ public static GroupDetailTotalDto as( groupDetailDto.groupProfileUrl(), groupDetailDto.createdAt(), groupCapsuleTotalCount, - canGroupEdit, + groupDetailDto.isOwner(), membersWithRelation ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index 2760a269e..a3294b43d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -17,6 +17,4 @@ Slice findGroupsSlice( Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId); - - Boolean findGroupEditPermission(final Long groupId, final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index 6d42c08a2..bffb4dd09 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -6,9 +6,12 @@ import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; +import com.querydsl.core.types.Expression; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -58,13 +61,20 @@ public Slice findGroupsSlice( return new SliceImpl<>(groups, Pageable.ofSize(size), groups.size() > size); } + /** + * 사용자를 제외한 그룹원 정보와 그룹의 상세정보를 반환한다. + * @param groupId 상세정보를 찾을 그룹 아이디 + * @param memberId 사용자 아이디 + * @return 그룹의 상세정보({@code memberId} 제외 그룹원) + */ public Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId) { GroupDetailDto groupDetailDtoIncludeMe = jpaQueryFactory .selectFrom(group) - .join(memberGroup).on(memberGroup.group.id.eq(groupId)) + .join(memberGroup).on(memberGroup.group.id.eq(group.id)) .join(member).on(member.id.eq(memberGroup.member.id)) + .where(group.id.eq(groupId)) .transform( groupBy(group.id).as( Projections.constructor( @@ -73,6 +83,7 @@ public Optional findGroupDetailByGroupIdAndMemberId(final Long g group.groupDescription, group.groupProfileUrl, group.createdAt, + Expressions.asBoolean(Boolean.FALSE), list( Projections.constructor( GroupMemberDto.class, @@ -92,9 +103,15 @@ public Optional findGroupDetailByGroupIdAndMemberId(final Long g return Optional.empty(); } - List groupMemberDtosExcludeMe = groupDetailDtoIncludeMe.members().stream() - .filter(memberDto -> !memberDto.memberId().equals(memberId)) - .toList(); + boolean isOwner = false; + List groupMemberDtosExcludeMe = new ArrayList<>(); + for (GroupMemberDto dto : groupDetailDtoIncludeMe.members()) { + if (!dto.memberId().equals(memberId)) { + groupMemberDtosExcludeMe.add(dto); + } else { + isOwner = dto.isOwner(); + } + } return Optional.of( GroupDetailDto.as( @@ -102,14 +119,9 @@ public Optional findGroupDetailByGroupIdAndMemberId(final Long g groupDetailDtoIncludeMe.groupDescription(), groupDetailDtoIncludeMe.groupProfileUrl(), groupDetailDtoIncludeMe.createdAt(), - groupMemberDtosExcludeMe) + isOwner, + groupMemberDtosExcludeMe + ) ); } - - public Boolean findGroupEditPermission(final Long groupId, final Long memberId) { - return jpaQueryFactory.select(memberGroup.isOwner) - .from(memberGroup) - .where(memberGroup.group.id.eq(groupId).and(memberGroup.member.id.eq(memberId))) - .fetchOne(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index c971191f7..d4d22f3a8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -43,13 +43,12 @@ public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final L groupId, memberId).orElseThrow(GroupNotFoundException::new); final Long groupCapsuleCount = groupCapsuleQueryRepository.findGroupCapsuleCount(groupId); - final Boolean canGroupEdit = groupRepository.findGroupEditPermission(groupId, memberId); final List groupMemberIds = groupDetailDto.members().stream() .map(GroupMemberDto::memberId) .toList(); final List friendIds = memberFriendRepository.findFriendIds(groupMemberIds, memberId); - return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, canGroupEdit, friendIds); + return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, friendIds); } } From 2669e08159630a21075b159e361b68ef5ba04822 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 12:56:34 +0900 Subject: [PATCH 065/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberGroupQueryRepositoryTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename backend/core/src/test/java/site/timecapsulearchive/core/domain/{group => member_group}/repository/MemberGroupQueryRepositoryTest.java (97%) 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/member_group/repository/MemberGroupQueryRepositoryTest.java similarity index 97% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/MemberGroupQueryRepositoryTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java index fcb0ab5ab..fc4856904 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/member_group/repository/MemberGroupQueryRepositoryTest.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.group.repository; +package site.timecapsulearchive.core.domain.member_group.repository; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -26,6 +26,8 @@ import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; +import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepository; +import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepositoryImpl; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; From 0d57236decd41c533e1c09da5ec16753bff87281 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 12:56:44 +0900 Subject: [PATCH 066/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=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 --- .../GroupQueryRepositoryImplTest.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java new file mode 100644 index 000000000..239a7c9a2 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java @@ -0,0 +1,129 @@ +package site.timecapsulearchive.core.domain.group.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import java.util.List; +import java.util.Optional; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestConstructor; +import org.springframework.test.context.TestConstructor.AutowireMode; +import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.common.RepositoryTest; +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.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.entity.Group; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; + +@TestConstructor(autowireMode = AutowireMode.ALL) +class GroupQueryRepositoryImplTest extends RepositoryTest { + + private final GroupQueryRepository groupQueryRepository; + + private Long groupId; + private Long ownerId; + private Long groupMemberId; + + GroupQueryRepositoryImplTest(JPAQueryFactory jpaQueryFactory) { + this.groupQueryRepository = new GroupQueryRepositoryImpl(jpaQueryFactory); + } + + @Transactional + @BeforeEach + void setup(@Autowired EntityManager entityManager) { + Group group = GroupFixture.group(); + entityManager.persist(group); + groupId = group.getId(); + + Member member = MemberFixture.member(1); + entityManager.persist(member); + ownerId = member.getId(); + + MemberGroup memberGroup = MemberGroupFixture.groupOwner(member, group); + entityManager.persist(memberGroup); + + List members = MemberFixture.members(2, 10); + for (Member m : members) { + entityManager.persist(m); + + MemberGroup mg = MemberGroupFixture.memberGroup(m, group, Boolean.FALSE); + entityManager.persist(mg); + } + groupMemberId = members.get(0).getId(); + } + + @Test + void 그룹_아이디로_그룹을_상세정보를_조회하면_그룹의_상세정보를_볼_수_있다() { + //given + //when + GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, ownerId) + .orElseThrow(); + + //then + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(groupDetailDto.groupName()).isNotBlank(); + softly.assertThat(groupDetailDto.groupDescription()).isNotBlank(); + softly.assertThat(groupDetailDto.groupProfileUrl()).isNotBlank(); + softly.assertThat(groupDetailDto.groupProfileUrl()).isNotBlank(); + softly.assertThat(groupDetailDto.createdAt()).isNotNull(); + softly.assertThat(groupDetailDto.members()).isNotEmpty(); + }); + } + + @Test + void 그룹_아이디로_그룹을_상세정보를_조회하면_사용자를_제외한_그룹원의_정보를_볼_수_있다() { + //given + //when + GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, ownerId) + .orElseThrow(); + + //then + assertThat(groupDetailDto.members()).noneMatch(m -> m.memberId().equals(ownerId)); + } + + @Test + void 존재하지_않는_그룹_아이디로_그룹을_상세정보를_조회하면_예외가_발생한다() { + //given + Long notExistGroupId = 999L; + + //when + Optional groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + notExistGroupId, ownerId); + + //then + assertThat(groupDetailDto).isEmpty(); + } + + @Test + void 그룹장이_그룹을_상세정보를_조회하면_그룹의_수정권한을_가진다() { + //given + //when + GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, ownerId) + .orElseThrow(); + + //then + assertThat(groupDetailDto.isOwner()).isTrue(); + } + + @Test + void 그룹원이_그룹을_상세정보를_조회하면_그룹의_수정권한이_없다() { + //given + //when + GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, groupMemberId) + .orElseThrow(); + + //then + assertThat(groupDetailDto.isOwner()).isFalse(); + } +} \ No newline at end of file From 9870909b4403265f8d1472e574aeca9cf8a03706 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 16:03:23 +0900 Subject: [PATCH 067/195] fix : flyway -> datasource script run --- .../MemberFriendQueryRepositoryTest.java | 14 ++++++++++++++ ...mber_tag_insert.sql => member_tag_insert.sql} | 16 ++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) rename backend/core/src/test/resources/{db/migration/V999__member_tag_insert.sql => member_tag_insert.sql} (80%) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index 6e9105075..f66278ae5 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -13,12 +13,17 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import javax.sql.DataSource; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.data.domain.Slice; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; import site.timecapsulearchive.core.common.RepositoryTest; @@ -37,6 +42,7 @@ @TestConstructor(autowireMode = AutowireMode.ALL) class MemberFriendQueryRepositoryTest extends RepositoryTest { + private static final String SQL_SCRIP_NAME = "member_tag_insert.sql"; private static final int MAX_COUNT = 10; private static final Long FRIEND_START_ID = 1L; private static final Long FRIEND_ID_TO_INVITE_OWNER = 11L; @@ -55,6 +61,14 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { new JPAQueryFactory(entityManager)); } + @BeforeAll + static void insertScript(@Autowired DataSource dataSource) { + Resource resource = new ClassPathResource(SQL_SCRIP_NAME); + ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator( + resource); + resourceDatabasePopulator.execute(dataSource); + } + @BeforeEach void setup(@Autowired EntityManager entityManager) { Member owner = MemberFixture.member(0); diff --git a/backend/core/src/test/resources/db/migration/V999__member_tag_insert.sql b/backend/core/src/test/resources/member_tag_insert.sql similarity index 80% rename from backend/core/src/test/resources/db/migration/V999__member_tag_insert.sql rename to backend/core/src/test/resources/member_tag_insert.sql index 5bd0618bb..79fc14af9 100644 --- a/backend/core/src/test/resources/db/migration/V999__member_tag_insert.sql +++ b/backend/core/src/test/resources/member_tag_insert.sql @@ -6,13 +6,13 @@ values (true, false, now(), 9999, now(), null, 'test_nickname_9999', 'KAKAO', ' (true, false, now(), 10003, now(), null, 'test_nickname_10003', 'KAKAO', 'test_email_10003@gmail.co', null, 'http://test_profile_url_10003.com', 'test_auth_id_10003', null, 'test_tag_10003', null); insert into member_friend(member_friend_id, owner_id, friend_id, created_at, updated_at) -values (1, 10000, 9999, now(), now()), - (2, 9999, 10000, now(), now()), - (3, 9999, 10001, now(), now()), - (4, 10001, 9999, now(), now()); +values (999, 10000, 9999, now(), now()), + (1000, 9999, 10000, now(), now()), + (1001, 9999, 10001, now(), now()), + (1002, 10001, 9999, now(), now()); insert into friend_invite(friend_invite_id, owner_id, friend_id, created_at, updated_at) -values (1, 10002, 9999, now(), now()), - (2, 9999, 10002, now(), now()), - (3, 9999, 10003, now(), now()), - (4, 10003, 9999, now(), now()); \ No newline at end of file +values (999, 10002, 9999, now(), now()), + (1000, 9999, 10002, now(), now()), + (1001, 9999, 10003, now(), now()), + (1002, 10003, 9999, now(), now()); \ No newline at end of file From c897ee120f0387a8ccb584e50ffeb574d2bcabfd Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 16:13:43 +0900 Subject: [PATCH 068/195] =?UTF-8?q?fix=20:=20=EC=98=81=EC=86=8D=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20flush=20&=20clear?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../friend/repository/MemberFriendQueryRepositoryTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index f66278ae5..d334b00be 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -105,6 +105,9 @@ void setup(@Autowired EntityManager entityManager) { entityManager.persist(notFriend); hashedNotFriendPhones.add(notFriend.getPhone_hash()); } + + entityManager.flush(); + entityManager.clear(); } @ParameterizedTest From 5bcc37fa1bd2123048ce306617582e46b4a79549 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 17:49:40 +0900 Subject: [PATCH 069/195] =?UTF-8?q?fix=20:=20sql=20script=20->=20Transacti?= =?UTF-8?q?onTemplate=20=EC=BB=A4=EB=B0=8B=20=EC=8B=9C=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MemberFriendQueryRepositoryTest.java | 157 +++++++++--------- .../src/test/resources/member_tag_insert.sql | 18 -- 2 files changed, 79 insertions(+), 96 deletions(-) delete mode 100644 backend/core/src/test/resources/member_tag_insert.sql diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index d334b00be..92df26177 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -13,19 +13,17 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import javax.sql.DataSource; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; import org.springframework.data.domain.Slice; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; import site.timecapsulearchive.core.common.RepositoryTest; import site.timecapsulearchive.core.common.fixture.domain.FriendInviteFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; @@ -42,11 +40,11 @@ @TestConstructor(autowireMode = AutowireMode.ALL) class MemberFriendQueryRepositoryTest extends RepositoryTest { - private static final String SQL_SCRIP_NAME = "member_tag_insert.sql"; + private static String PROPAGATION_REQUIRES_NEW = "PROPAGATION_REQUIRES_NEW"; private static final int MAX_COUNT = 10; - private static final Long FRIEND_START_ID = 1L; - private static final Long FRIEND_ID_TO_INVITE_OWNER = 11L; - private static final Long NOT_FRIEND_MEMBER_START_ID = 12L; + private static final Long FRIEND_START_ID = 2L; + private static final Long FRIEND_ID_TO_INVITE_OWNER = 12L; + private static final Long NOT_FRIEND_MEMBER_START_ID = 13L; private final MemberFriendQueryRepository memberFriendQueryRepository; @@ -55,59 +53,76 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private final List hashedNotFriendPhones = new ArrayList<>(); private Long ownerId; private Long friendId; + private String friendTag; + private String notFriendTag; + private String friendInviteTag; + private String notFriendInviteTag; MemberFriendQueryRepositoryTest(@Autowired EntityManager entityManager) { this.memberFriendQueryRepository = new MemberFriendQueryRepositoryImpl( new JPAQueryFactory(entityManager)); } - @BeforeAll - static void insertScript(@Autowired DataSource dataSource) { - Resource resource = new ClassPathResource(SQL_SCRIP_NAME); - ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator( - resource); - resourceDatabasePopulator.execute(dataSource); + @BeforeEach + void setup(@Autowired EntityManager entityManager, + @Autowired PlatformTransactionManager platformTransactionManager) { + TransactionTemplate transactionTemplate = new TransactionTemplate( + platformTransactionManager); + transactionTemplate.setPropagationBehaviorName(PROPAGATION_REQUIRES_NEW); + + transactionTemplate.executeWithoutResult(status -> { + // owner 데이터 + Member owner = MemberFixture.member(1); + entityManager.persist(owner); + ownerId = owner.getId(); + + // owner와 친구 관계를 맺는 데이터 + List friends = MemberFixture.members(FRIEND_START_ID.intValue(), MAX_COUNT); + for (Member friend : friends) { + entityManager.persist(friend); + hashedFriendPhones.add(friend.getPhone_hash()); + + MemberFriend memberFriend = MemberFriendFixture.memberFriend(owner, friend); + entityManager.persist(memberFriend); + + MemberFriend friendMember = MemberFriendFixture.memberFriend(friend, owner); + entityManager.persist(friendMember); + } + friendId = friends.get(0).getId(); + friendTag = friends.get(0).getTag(); + + // owner에게 친구 요청만 보낸 멤버 데이터 + Member inviteFriendToOwner = MemberFixture.member(FRIEND_ID_TO_INVITE_OWNER.intValue()); + entityManager.persist(inviteFriendToOwner); + friendInviteTag =inviteFriendToOwner.getTag(); + + FriendInvite friendInvite = FriendInviteFixture.friendInvite(inviteFriendToOwner, + owner); + entityManager.persist(friendInvite); + + //owner와 친구가 아닌 멤버 데이터 + List notFriendMembers = MemberFixture.members( + NOT_FRIEND_MEMBER_START_ID.intValue(), + MAX_COUNT); + for (Member notFriend : notFriendMembers) { + entityManager.persist(notFriend); + hashedNotFriendPhones.add(notFriend.getPhone_hash()); + } + + //owner에게 친구 요청을 보내지 않은 데이터 + notFriendInviteTag = notFriendMembers.get(0).getTag(); + //owner와 친구가 아닌 데이터 + notFriendTag = notFriendMembers.get(0).getTag(); + }); } - @BeforeEach - void setup(@Autowired EntityManager entityManager) { - Member owner = MemberFixture.member(0); - entityManager.persist(owner); - ownerId = owner.getId(); - - // owner와 친구 관계를 맺는 데이터 - List friends = MemberFixture.members(FRIEND_START_ID.intValue(), MAX_COUNT); - for (Member friend : friends) { - entityManager.persist(friend); - hashedFriendPhones.add(friend.getPhone_hash()); - - MemberFriend memberFriend = MemberFriendFixture.memberFriend(owner, friend); - entityManager.persist(memberFriend); - - MemberFriend friendMember = MemberFriendFixture.memberFriend(friend, owner); - entityManager.persist(friendMember); - } - friendId = friends.get(0).getId(); - - // owner에게 요청만 보낸 데이터 - Member inviteFriendToOwner = MemberFixture.member(FRIEND_ID_TO_INVITE_OWNER.intValue()); - entityManager.persist(inviteFriendToOwner); - - FriendInvite friendInvite = FriendInviteFixture.friendInvite(inviteFriendToOwner, - owner); - entityManager.persist(friendInvite); - - //owner와 친구가 아닌 멤버 데이터 - List notFriendMembers = MemberFixture.members( - NOT_FRIEND_MEMBER_START_ID.intValue(), - MAX_COUNT); - for (Member notFriend : notFriendMembers) { - entityManager.persist(notFriend); - hashedNotFriendPhones.add(notFriend.getPhone_hash()); - } - - entityManager.flush(); - entityManager.clear(); + @AfterEach + void clear(@Autowired EntityManager entityManager) { + entityManager.createNativeQuery("SET FOREIGN_KEY_CHECKS=0").executeUpdate(); + entityManager.createNativeQuery("TRUNCATE TABLE friend_invite").executeUpdate(); + entityManager.createNativeQuery("TRUNCATE TABLE member_friend").executeUpdate(); + entityManager.createNativeQuery("TRUNCATE TABLE member").executeUpdate(); + entityManager.createNativeQuery("SET FOREIGN_KEY_CHECKS=1").executeUpdate(); } @ParameterizedTest @@ -151,7 +166,7 @@ void setup(@Autowired EntityManager entityManager) { } @Test - void 친구_요청만_보낸_사용자가_친구_목록_조회하면_빈_리스트가_나온다() { + void 친구_요청만_보낸_사용자가_친구_목록_조회하면_리스트가_나온다() { //given int size = 20; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); @@ -294,7 +309,7 @@ void setup(@Autowired EntityManager entityManager) { @Test void 태그로_검색하면_가장_비슷한_태그를_가진_사용자_한_명만_반환한다() { //given - String tag = "test_tag"; + String tag = "testTag"; //when Optional dto = memberFriendQueryRepository.findFriendsByTag( @@ -307,14 +322,12 @@ void setup(@Autowired EntityManager entityManager) { @Test void 일치하는_태그로_검색하면_일치하는_태그를_가진_사용자_한_명만만_나온다() { //given - String tag = "test_tag_9999"; - //when SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( - ownerId, tag).orElseThrow(); + ownerId, friendTag).orElseThrow(); //then - assertThat(dto.nickname()).isEqualTo("test_nickname_9999"); + assertThat(dto.id()).isEqualTo(friendId); } @Test @@ -331,12 +344,9 @@ void setup(@Autowired EntityManager entityManager) { @Test void 사용자가_친구_태그로_친구관계를_조회하면_친구인_경우_True를_반환한다() { //given - String tag = "test_tag_9999"; - Long ownerId = 10000L; - //when SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( - ownerId, tag).orElseThrow(); + ownerId, friendTag).orElseThrow(); //then assertThat(dto.isFriend()).isTrue(); @@ -345,12 +355,9 @@ void setup(@Autowired EntityManager entityManager) { @Test void 사용자가_친구_태그로_친구관계를_조회하면_친구가_아닌_경우_False를_반환한다() { //given - String tag = "test_tag_9999"; - Long ownerId = 10003L; - //when SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( - ownerId, tag).orElseThrow(); + ownerId, notFriendTag).orElseThrow(); //then assertThat(dto.isFriend()).isFalse(); @@ -359,28 +366,22 @@ void setup(@Autowired EntityManager entityManager) { @Test void 사용자가_친구_태그로_친구초대관계를_조회하면_친구_초대한_경우_True를_반환한다() { //given - String tag = "test_tag_9999"; - Long ownerId = 10001L; - //when SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( - ownerId, tag).orElseThrow(); + ownerId, friendInviteTag).orElseThrow(); //then - assertThat(dto.isFriend()).isTrue(); + assertThat(dto.isFriendInviteToMe()).isTrue(); } @Test void 사용자가_친구_태그로_친구초대관계를_조회하면_친구_초대하지_않은_경우_False를_반환한다() { //given - String tag = "test_tag_9999"; - Long ownerId = 10003L; - //when SearchFriendSummaryDtoByTag dto = memberFriendQueryRepository.findFriendsByTag( - ownerId, tag).orElseThrow(); + ownerId, notFriendInviteTag).orElseThrow(); //then - assertThat(dto.isFriend()).isFalse(); + assertThat(dto.isFriendInviteToMe()).isFalse(); } } \ No newline at end of file diff --git a/backend/core/src/test/resources/member_tag_insert.sql b/backend/core/src/test/resources/member_tag_insert.sql deleted file mode 100644 index 79fc14af9..000000000 --- a/backend/core/src/test/resources/member_tag_insert.sql +++ /dev/null @@ -1,18 +0,0 @@ -insert into member (is_verified, notification_enabled, created_at, member_id, updated_at, phone, nickname, social_type, email, fcm_token, profile_url, auth_id, phone_hash, tag, password) -values (true, false, now(), 9999, now(), null, 'test_nickname_9999', 'KAKAO', 'test_email_9999@gmail.com', null, 'http://test_profile_url_9999.com', 'test_auth_id_9999', null, 'test_tag_9999', null), - (true, false, now(), 10000, now(), null, 'test_nickname_10000', 'KAKAO', 'test_email_10000@gmail.co', null, 'http://test_profile_url_10000.com', 'test_auth_id_10000', null, 'test_tag_10000', null), - (true, false, now(), 10001, now(), null, 'test_nickname_10001', 'KAKAO', 'test_email_10001@gmail.co', null, 'http://test_profile_url_10001.com', 'test_auth_id_10001', null, 'test_tag_10001', null), - (true, false, now(), 10002, now(), null, 'test_nickname_10002', 'KAKAO', 'test_email_10002@gmail.co', null, 'http://test_profile_url_10002.com', 'test_auth_id_10002', null, 'test_tag_10002', null), - (true, false, now(), 10003, now(), null, 'test_nickname_10003', 'KAKAO', 'test_email_10003@gmail.co', null, 'http://test_profile_url_10003.com', 'test_auth_id_10003', null, 'test_tag_10003', null); - -insert into member_friend(member_friend_id, owner_id, friend_id, created_at, updated_at) -values (999, 10000, 9999, now(), now()), - (1000, 9999, 10000, now(), now()), - (1001, 9999, 10001, now(), now()), - (1002, 10001, 9999, now(), now()); - -insert into friend_invite(friend_invite_id, owner_id, friend_id, created_at, updated_at) -values (999, 10002, 9999, now(), now()), - (1000, 9999, 10002, now(), now()), - (1001, 9999, 10003, now(), now()), - (1002, 10003, 9999, now(), now()); \ No newline at end of file From a907a97503a7039231fddccd2c31726e6e98bde6 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 17:56:47 +0900 Subject: [PATCH 070/195] =?UTF-8?q?fix=20:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member_friend/MemberFriendQueryRepositoryImpl.java | 8 ++++---- .../repository/MemberFriendQueryRepositoryTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index e58fdc147..7bc29fdd2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -173,12 +173,12 @@ public Optional findFriendsByTag( final QFriendInvite friendInviteToFriend = new QFriendInvite(FRIEND_INVITE_TO_FRIEND_PATH); final QFriendInvite friendInviteToMe = new QFriendInvite(FRIEND_INVITE_TO_ME_PATH); - NumberTemplate tagMatchTemplate = Expressions.numberTemplate(Double.class, + NumberTemplate tagFullTextSearchTemplate = Expressions.numberTemplate(Double.class, MATCH_AGAINST_FUNCTION, member.tag, tag); - OrderSpecifier tagEqCaseDesc = new CaseBuilder().when(member.tag.eq(tag)) + OrderSpecifier tagFullyMatchFirstOrder = new CaseBuilder().when(member.tag.eq(tag)) .then(Boolean.TRUE) .otherwise(Boolean.FALSE).desc(); @@ -203,8 +203,8 @@ public Optional findFriendsByTag( .leftJoin(friendInviteToMe) .on(friendInviteToMe.owner.id.eq(member.id) .and(friendInviteToMe.friend.id.eq(memberId))) - .where(tagMatchTemplate.gt(MATCH_THRESHOLD)) - .orderBy(tagEqCaseDesc, tagMatchTemplate.desc()) + .where(tagFullTextSearchTemplate.gt(MATCH_THRESHOLD)) + .orderBy(tagFullyMatchFirstOrder, tagFullTextSearchTemplate.desc()) .limit(1L) .fetchOne() ); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index 92df26177..09ae4cf3d 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -40,7 +40,7 @@ @TestConstructor(autowireMode = AutowireMode.ALL) class MemberFriendQueryRepositoryTest extends RepositoryTest { - private static String PROPAGATION_REQUIRES_NEW = "PROPAGATION_REQUIRES_NEW"; + private static final String PROPAGATION_REQUIRES_NEW = "PROPAGATION_REQUIRES_NEW"; private static final int MAX_COUNT = 10; private static final Long FRIEND_START_ID = 2L; private static final Long FRIEND_ID_TO_INVITE_OWNER = 12L; From f3f5f39a8f41a026475c0f141eacfbd5a0d51e51 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 29 May 2024 21:44:12 +0900 Subject: [PATCH 071/195] =?UTF-8?q?test=20:=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupQueryRepositoryImplTest.java | 64 ++++++++++++++++++ .../MemberGroupQueryRepositoryTest.java | 67 ------------------- 2 files changed, 64 insertions(+), 67 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java index 239a7c9a2..3b07e0ac3 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java @@ -18,6 +18,7 @@ import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberGroupFixture; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; @@ -59,6 +60,69 @@ void setup(@Autowired EntityManager entityManager) { groupMemberId = members.get(0).getId(); } + @Test + void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_상세가_반환된다() { + //given + //when + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, ownerId).orElseThrow(); + + //then + assertThat(groupDetail).isNotNull(); + } + + @Test + void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_정보를_볼_수_있다() { + //given + //when + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, ownerId).orElseThrow(); + + //then + SoftAssertions.assertSoftly(softly -> { + assertThat(groupDetail.groupName()).isNotBlank(); + assertThat(groupDetail.groupDescription()).isNotBlank(); + assertThat(groupDetail.groupProfileUrl()).isNotBlank(); + assertThat(groupDetail.createdAt()).isNotNull(); + }); + } + + @Test + void 그룹_아이디와_멤버_아이디_그룹을_조회하면_그룹원들의_정보를_볼_수_있다() { + //given + //when + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, groupMemberId).orElseThrow(); + + //then + SoftAssertions.assertSoftly(softly -> { + assertThat(groupDetail.members()).isNotEmpty(); + assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.memberId()).isNotNull()); + assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.tag()).isNotBlank()); + assertThat(groupDetail.members()).allSatisfy( + m -> assertThat(m.nickname()).isNotBlank()); + assertThat(groupDetail.members()).allSatisfy( + m -> assertThat(m.profileUrl()).isNotBlank()); + assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.isOwner()).isNotNull()); + }); + } + + @Test + void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_본인은_포함되어_조회되지_않는다() { + //given + //when + GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( + groupId, groupMemberId).orElseThrow(); + + List groupMemberDtos = groupDetail.members(); + + //then + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(groupMemberDtos) + .noneMatch(member -> member.memberId().equals(groupMemberId)); + }); + } + @Test void 그룹_아이디로_그룹을_상세정보를_조회하면_그룹의_상세정보를_볼_수_있다() { //given diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java index fc4856904..c0a2329ef 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java @@ -40,7 +40,6 @@ class MemberGroupQueryRepositoryTest extends RepositoryTest { private Long memberId; private Long memberIdWithNoGroup; - private Long ownerGroupId; MemberGroupQueryRepositoryTest(JPAQueryFactory jpaQueryFactory) { this.groupQueryRepository = new GroupQueryRepositoryImpl(jpaQueryFactory); @@ -67,9 +66,6 @@ void setup(@Autowired EntityManager entityManager) { groups.add(group); } - //사용자가 그룹장인 그룹아이디 - ownerGroupId = groups.get(0).getId(); - //그룹원들 List members = MemberFixture.members(4, 2); members.forEach(entityManager::persist); @@ -150,67 +146,4 @@ void setup(@Autowired EntityManager entityManager) { //then assertThat(groupsSlice.isEmpty()).isTrue(); } - - @Test - void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_상세가_반환된다() { - //given - //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( - ownerGroupId, memberId).orElseThrow(); - - //then - assertThat(groupDetail).isNotNull(); - } - - @Test - void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_정보를_볼_수_있다() { - //given - //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( - ownerGroupId, memberId).orElseThrow(); - - //then - SoftAssertions.assertSoftly(softly -> { - assertThat(groupDetail.groupName()).isNotBlank(); - assertThat(groupDetail.groupDescription()).isNotBlank(); - assertThat(groupDetail.groupProfileUrl()).isNotBlank(); - assertThat(groupDetail.createdAt()).isNotNull(); - }); - } - - @Test - void 그룹_아이디와_멤버_아이디_그룹을_조회하면_그룹원들의_정보를_볼_수_있다() { - //given - //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( - ownerGroupId, memberId).orElseThrow(); - - //then - SoftAssertions.assertSoftly(softly -> { - assertThat(groupDetail.members()).isNotEmpty(); - assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.memberId()).isNotNull()); - assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.tag()).isNotBlank()); - assertThat(groupDetail.members()).allSatisfy( - m -> assertThat(m.nickname()).isNotBlank()); - assertThat(groupDetail.members()).allSatisfy( - m -> assertThat(m.profileUrl()).isNotBlank()); - assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.isOwner()).isNotNull()); - }); - } - - @Test - void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_본인은_포함되어_조회되지_않는다() { - //given - //when - GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( - ownerGroupId, memberId).orElseThrow(); - - List groupMemberDtos = groupDetail.members(); - - //then - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(groupMemberDtos) - .noneMatch(member -> member.memberId().equals(memberId)); - }); - } } \ No newline at end of file From 1c8b937e0684abe7633101d9a4410970b51fc82f Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 30 May 2024 00:16:06 +0900 Subject: [PATCH 072/195] =?UTF-8?q?test=20:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupQueryRepositoryImplTest.java | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java index 3b07e0ac3..4909a0018 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImplTest.java @@ -84,6 +84,7 @@ void setup(@Autowired EntityManager entityManager) { assertThat(groupDetail.groupDescription()).isNotBlank(); assertThat(groupDetail.groupProfileUrl()).isNotBlank(); assertThat(groupDetail.createdAt()).isNotNull(); + assertThat(groupDetail.members()).isNotEmpty(); }); } @@ -123,25 +124,6 @@ void setup(@Autowired EntityManager entityManager) { }); } - @Test - void 그룹_아이디로_그룹을_상세정보를_조회하면_그룹의_상세정보를_볼_수_있다() { - //given - //when - GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId( - groupId, ownerId) - .orElseThrow(); - - //then - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(groupDetailDto.groupName()).isNotBlank(); - softly.assertThat(groupDetailDto.groupDescription()).isNotBlank(); - softly.assertThat(groupDetailDto.groupProfileUrl()).isNotBlank(); - softly.assertThat(groupDetailDto.groupProfileUrl()).isNotBlank(); - softly.assertThat(groupDetailDto.createdAt()).isNotNull(); - softly.assertThat(groupDetailDto.members()).isNotEmpty(); - }); - } - @Test void 그룹_아이디로_그룹을_상세정보를_조회하면_사용자를_제외한_그룹원의_정보를_볼_수_있다() { //given From de8eebdb5bb4d59bea54b50919769521a296a743 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 30 May 2024 13:08:24 +0900 Subject: [PATCH 073/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/query/GroupQueryApiController.java | 4 +- .../group/data/dto/FinalGroupSummaryDto.java | 27 +++++++++++++ .../group/data/dto/GroupSummaryDto.java | 13 ------- .../data/response/GroupSummaryResponse.java | 7 +++- .../data/response/GroupsSliceResponse.java | 4 +- .../repository/GroupQueryRepository.java | 4 +- .../repository/GroupQueryRepositoryImpl.java | 39 ++++++++++++++++--- .../service/query/GroupQueryService.java | 4 +- 8 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java 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 index 4836f71d9..7e1b56c7d 100644 --- 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 @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; 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; @@ -57,7 +57,7 @@ public ResponseEntity> findGroups( @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, size, + final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, size, createdAt); return ResponseEntity.ok( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java new file mode 100644 index 000000000..6b8618f00 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java @@ -0,0 +1,27 @@ + +package site.timecapsulearchive.core.domain.group.data.dto; + +import java.util.function.Function; +import site.timecapsulearchive.core.domain.group.data.response.GroupSummaryResponse; + +public record FinalGroupSummaryDto( + + GroupSummaryDto groupSummaryDto, + String groupOwnerProfileUrl, + Long totalGroupMemberCount + +) { + + public GroupSummaryResponse toResponse(final Function preSignedUrlFunction) { + return GroupSummaryResponse.builder() + .id(groupSummaryDto.id()) + .name(groupSummaryDto.groupName()) + .groupOwnerProfileUrl(preSignedUrlFunction.apply(groupOwnerProfileUrl)) + .profileUrl(preSignedUrlFunction.apply(groupSummaryDto.groupProfileUrl())) + .totalGroupMemberCount(totalGroupMemberCount) + .createdAt(groupSummaryDto.createdAt()) + .isOwner(groupSummaryDto.isOwner()) + .build(); + } + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java index e1a9de247..73194d571 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupSummaryDto.java @@ -1,26 +1,13 @@ package site.timecapsulearchive.core.domain.group.data.dto; import java.time.ZonedDateTime; -import java.util.function.Function; -import site.timecapsulearchive.core.domain.group.data.response.GroupSummaryResponse; public record GroupSummaryDto( Long id, String groupName, - String groupDescription, String groupProfileUrl, ZonedDateTime createdAt, Boolean isOwner ) { - public GroupSummaryResponse toResponse(final Function preSignedUrlFunction) { - return GroupSummaryResponse.builder() - .id(id) - .name(groupName) - .description(groupDescription) - .profileUrl(preSignedUrlFunction.apply(groupProfileUrl)) - .createdAt(createdAt) - .isOwner(isOwner) - .build(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupSummaryResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupSummaryResponse.java index d43993dce..8856a6036 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupSummaryResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupSummaryResponse.java @@ -18,8 +18,11 @@ public record GroupSummaryResponse( @Schema(description = "그룹 프로필 url") String profileUrl, - @Schema(description = "그룹 설명") - String description, + @Schema(description = "그룹장 프로필 url") + String groupOwnerProfileUrl, + + @Schema(description = "총 그룹원 수") + Long totalGroupMemberCount, @Schema(description = "그룹 생성일") ZonedDateTime createdAt, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java index d0fd0c1af..f6174c2f7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java @@ -3,7 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import java.util.function.Function; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; @Schema(description = "사용자의 그룹 목록 응답") public record GroupsSliceResponse( @@ -16,7 +16,7 @@ public record GroupsSliceResponse( ) { public static GroupsSliceResponse createOf( - final List groups, + final List groups, final Boolean hasNext, final Function preSignedUrlFunction ) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index a3294b43d..d7e403a53 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -4,12 +4,12 @@ import java.util.Optional; 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.data.dto.FinalGroupSummaryDto; public interface GroupQueryRepository { - Slice findGroupsSlice( + Slice findGroupsSlice( final Long memberId, final int size, final ZonedDateTime createdAt diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index bffb4dd09..c9b0e033d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -6,7 +6,6 @@ import static site.timecapsulearchive.core.domain.member.entity.QMember.member; import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; -import com.querydsl.core.types.Expression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -15,6 +14,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -23,6 +23,7 @@ import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; @Repository @RequiredArgsConstructor @@ -30,7 +31,7 @@ public class GroupQueryRepositoryImpl implements GroupQueryRepository { private final JPAQueryFactory jpaQueryFactory; - public Slice findGroupsSlice( + public Slice findGroupsSlice( final Long memberId, final int size, final ZonedDateTime createdAt @@ -41,7 +42,6 @@ public Slice findGroupsSlice( GroupSummaryDto.class, group.id, group.groupName, - group.groupDescription, group.groupProfileUrl, group.createdAt, memberGroup.isOwner @@ -50,20 +50,49 @@ public Slice findGroupsSlice( .from(memberGroup) .join(memberGroup.group, group) .where(memberGroup.member.id.eq(memberId).and(memberGroup.createdAt.lt(createdAt))) + .orderBy(group.id.desc()) .limit(size + 1) .fetch(); + final List groupIds = groups.stream().map(GroupSummaryDto::id).toList(); + + final List groupOwnerProfileUrls = jpaQueryFactory + .select( + member.profileUrl + ) + .from(memberGroup) + .join(memberGroup.group, group) + .join(memberGroup.member, member) + .where(memberGroup.group.id.in(groupIds) + .and(memberGroup.isOwner.eq(true))) + .orderBy(group.id.desc()) + .fetch(); + + final List totalGroupMemberCount = jpaQueryFactory + .select(memberGroup.count()) + .from(memberGroup) + .where(memberGroup.group.id.in(groupIds)) + .groupBy(memberGroup.group.id) + .orderBy(memberGroup.group.id.desc()) + .fetch(); + final boolean hasNext = groups.size() > size; if (hasNext) { groups.remove(size); } - return new SliceImpl<>(groups, Pageable.ofSize(size), groups.size() > size); + List dtos = IntStream.range(0, groups.size()) + .mapToObj(i -> new FinalGroupSummaryDto(groups.get(i), + groupOwnerProfileUrls.get(i), totalGroupMemberCount.get(i))) + .toList(); + + return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); } /** * 사용자를 제외한 그룹원 정보와 그룹의 상세정보를 반환한다. - * @param groupId 상세정보를 찾을 그룹 아이디 + * + * @param groupId 상세정보를 찾을 그룹 아이디 * @param memberId 사용자 아이디 * @return 그룹의 상세정보({@code memberId} 제외 그룹원) */ diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index d4d22f3a8..82674de9a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -11,7 +11,7 @@ import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; @@ -30,7 +30,7 @@ public Group findGroupById(final Long groupId) { .orElseThrow(GroupNotFoundException::new); } - public Slice findGroupsSlice( + public Slice findGroupsSlice( final Long memberId, final int size, final ZonedDateTime createdAt From 9f565e97be65f92e5fd6e0cf58d8e1717d339fd9 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 30 May 2024 13:13:22 +0900 Subject: [PATCH 074/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MemberGroupQueryRepositoryTest.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java index c0a2329ef..88fb596db 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java @@ -8,7 +8,6 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; -import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -22,9 +21,7 @@ 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.domain.group.data.dto.GroupDetailDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepository; import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepositoryImpl; @@ -89,7 +86,7 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().plusDays(3); //when - Slice groupsSlice = groupQueryRepository.findGroupsSlice(memberId, + Slice groupsSlice = groupQueryRepository.findGroupsSlice(memberId, size, now); //then @@ -103,16 +100,19 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().plusDays(3); //when - List groupsSlice = groupQueryRepository.findGroupsSlice(memberId, + List groupsSlice = groupQueryRepository.findGroupsSlice(memberId, size, now).getContent(); //then assertSoftly(softly -> { - softly.assertThat(groupsSlice).allMatch(dto -> dto.id() != null); - softly.assertThat(groupsSlice).allMatch(dto -> !dto.groupName().isBlank()); - softly.assertThat(groupsSlice).allMatch(dto -> !dto.groupDescription().isBlank()); - softly.assertThat(groupsSlice).allMatch(dto -> !dto.groupProfileUrl().isBlank()); - softly.assertThat(groupsSlice).allMatch(dto -> dto.isOwner() != null); + softly.assertThat(groupsSlice).allMatch(dto -> dto.groupSummaryDto().id() != null); + softly.assertThat(groupsSlice) + .allMatch(dto -> !dto.groupSummaryDto().groupName().isBlank()); + softly.assertThat(groupsSlice) + .allMatch(dto -> !dto.groupSummaryDto().groupProfileUrl().isBlank()); + softly.assertThat(groupsSlice).allMatch(dto -> dto.groupSummaryDto().isOwner() != null); + softly.assertThat(groupsSlice).allMatch(dto -> dto.totalGroupMemberCount() != null); + softly.assertThat(groupsSlice).allMatch(dto -> !dto.groupOwnerProfileUrl().isBlank()); }); } @@ -123,7 +123,7 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().plusDays(3); //when - List groupsSlice = groupQueryRepository.findGroupsSlice( + List groupsSlice = groupQueryRepository.findGroupsSlice( memberIdWithNoGroup, size, now) @@ -140,7 +140,7 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().minusDays(5); //when - List groupsSlice = groupQueryRepository.findGroupsSlice(memberId, + List groupsSlice = groupQueryRepository.findGroupsSlice(memberId, size, now).getContent(); //then From a1cbd65a06900d81c7cd83d203301112a9d91a45 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 30 May 2024 13:45:57 +0900 Subject: [PATCH 075/195] =?UTF-8?q?refactor=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EC=83=9D=EC=84=B1=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 그룹원 아이디 삭제하고, 조회하여 수정 --- .../data/dto/GroupCapsuleCreateRequestDto.java | 1 - .../data/reqeust/GroupCapsuleCreateRequest.java | 5 ----- .../group_capsule/facade/GroupCapsuleFacade.java | 7 ++++++- .../domain/friend/api/query/FriendQueryApi.java | 8 ++++---- .../group/api/query/GroupQueryApiController.java | 5 +++-- .../domain/group/data/dto/FinalGroupSummaryDto.java | 1 - .../core/domain/group/data/dto/GroupDetailDto.java | 3 ++- .../group/repository/GroupQueryRepository.java | 2 +- .../group/repository/GroupQueryRepositoryImpl.java | 2 +- .../group/service/command/GroupCommandService.java | 13 ++++--------- .../group/service/query/GroupQueryService.java | 2 +- .../core/domain/member/service/MemberService.java | 1 - .../MemberGroupQueryRepository.java | 3 +++ .../MemberGroupQueryRepositoryImpl.java | 11 +++++++++-- .../service/MemberGroupQueryService.java | 9 +++++++++ .../core/global/util/TagGenerator.java | 10 ++++++---- 16 files changed, 49 insertions(+), 34 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleCreateRequestDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleCreateRequestDto.java index 603fd70bf..a98e16fdb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleCreateRequestDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleCreateRequestDto.java @@ -13,7 +13,6 @@ @Builder public record GroupCapsuleCreateRequestDto( - List groupMemberIds, List imageNames, List videoNames, Long capsuleSkinId, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/reqeust/GroupCapsuleCreateRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/reqeust/GroupCapsuleCreateRequest.java index 7754b3ae0..7d9b12843 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/reqeust/GroupCapsuleCreateRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/reqeust/GroupCapsuleCreateRequest.java @@ -14,10 +14,6 @@ @Schema(description = "그룹 캡슐 생성 포맷") public record GroupCapsuleCreateRequest( - @Schema(description = "그룹 멤버 아이디들") - @NotNull(message = "그룹 멤버 아이디들는 필수입니다.") - List groupMemberIds, - @Schema(description = "업로드한 이미지 경로 ex) xxx.jpg") List<@Image String> imageNames, @@ -56,7 +52,6 @@ public record GroupCapsuleCreateRequest( public GroupCapsuleCreateRequestDto toGroupCapsuleCreateRequestDto() { return GroupCapsuleCreateRequestDto.builder() - .groupMemberIds(groupMemberIds) .videoNames(videoNames) .imageNames(imageNames) .capsuleSkinId(capsuleSkinId) 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 de10d78e8..c4e160daa 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 @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.facade; +import java.util.List; import lombok.RequiredArgsConstructor; import org.locationtech.jts.geom.Point; import org.springframework.stereotype.Component; @@ -16,6 +17,7 @@ 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.domain.member_group.service.MemberGroupQueryService; import site.timecapsulearchive.core.global.geography.GeoTransformManager; @Component @@ -25,6 +27,7 @@ public class GroupCapsuleFacade { private final MemberService memberService; private final GroupCapsuleService groupCapsuleService; private final GroupQueryService groupQueryService; + private final MemberGroupQueryService memberGroupQueryService; private final ImageService imageService; private final VideoService videoService; private final GeoTransformManager geoTransformManager; @@ -51,6 +54,8 @@ public void saveGroupCapsule( imageService.bulkSave(dto.imageNames(), capsule, member); videoService.bulkSave(dto.videoNames(), capsule, member); - groupCapsuleOpenService.bulkSave(dto.groupMemberIds(), capsule); + final List groupMemberIds = memberGroupQueryService.findGroupMemberIds(groupId); + + groupCapsuleOpenService.bulkSave(groupMemberIds, capsule); } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java index 0cd05496a..ad85af51d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java @@ -106,10 +106,10 @@ ResponseEntity> searchMembersByPhones( @Operation( summary = "친구 검색", description = """ - 친구의 tag로 친구 검색을 한다. -
- 태그가 일치하면 일치하는 태그를 가진 사용자를 일치하지 않으면 가장 비슷한 태그를 가진 사용자를 반환한다. - """, + 친구의 tag로 친구 검색을 한다. +
+ 태그가 일치하면 일치하는 태그를 가진 사용자를 일치하지 않으면 가장 비슷한 태그를 가진 사용자를 반환한다. + """, security = {@SecurityRequirement(name = "user_token")}, tags = {"friend"} ) 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 index 7e1b56c7d..6e7e200f8 100644 --- 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 @@ -10,8 +10,8 @@ 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.GroupDetailTotalDto; import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; 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; @@ -57,7 +57,8 @@ public ResponseEntity> findGroups( @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, size, + final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, + size, createdAt); return ResponseEntity.ok( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java index 6b8618f00..f545d91fb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java @@ -1,4 +1,3 @@ - package site.timecapsulearchive.core.domain.group.data.dto; import java.util.function.Function; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java index d7852ebdf..5401ade5b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailDto.java @@ -20,7 +20,8 @@ public static GroupDetailDto as( Boolean isOwner, List members ) { - return new GroupDetailDto(groupName, groupDescription, groupProfileUrl, createdAt, isOwner, members); + return new GroupDetailDto(groupName, groupDescription, groupProfileUrl, createdAt, isOwner, + members); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index d7e403a53..3affaae9b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -3,8 +3,8 @@ import java.time.ZonedDateTime; import java.util.Optional; import org.springframework.data.domain.Slice; -import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; public interface GroupQueryRepository { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index c9b0e033d..398d1d64f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -20,10 +20,10 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; -import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; @Repository @RequiredArgsConstructor diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index c15b73e01..d6c78dbd4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -4,8 +4,6 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.TransactionStatus; -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; @@ -48,13 +46,10 @@ public void createGroup(final Long memberId, final GroupCreateDto dto) { final MemberGroup memberGroup = MemberGroup.createGroupOwner(member, group); - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - groupRepository.save(group); - memberGroupRepository.save(memberGroup); - groupInviteRepository.bulkSave(memberId, group.getId(), dto.targetIds()); - } + transactionTemplate.executeWithoutResult(status -> { + groupRepository.save(group); + memberGroupRepository.save(memberGroup); + groupInviteRepository.bulkSave(memberId, group.getId(), dto.targetIds()); }); socialNotificationManager.sendGroupInviteMessage(member.getNickname(), diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index 82674de9a..c1f996772 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -8,10 +8,10 @@ import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; +import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; -import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.group.repository.GroupRepository; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 023ac2d41..2bf412d28 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -1,7 +1,6 @@ package site.timecapsulearchive.core.domain.member.service; import java.time.ZonedDateTime; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Slice; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java index 963047020..81bcb5d2e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; +import java.util.List; import java.util.Optional; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; @@ -8,4 +9,6 @@ public interface MemberGroupQueryRepository { Optional findOwnerInMemberGroup(Long groupId, Long memberId); Optional findIsOwnerByMemberIdAndGroupId(Long groupOwnerId, Long groupId); + + Optional> findGroupMemberIds(Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 8aa557ac2..22d9a6035 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -6,6 +6,7 @@ import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -17,7 +18,6 @@ public class MemberGroupQueryRepositoryImpl implements MemberGroupQueryRepositor private final JPAQueryFactory jpaQueryFactory; - @Override public Optional findOwnerInMemberGroup(final Long groupId, final Long memberId) { return Optional.ofNullable(jpaQueryFactory @@ -38,7 +38,6 @@ public Optional findOwnerInMemberGroup(final Long groupId, ); } - @Override public Optional findIsOwnerByMemberIdAndGroupId( final Long memberId, final Long groupId @@ -51,4 +50,12 @@ public Optional findIsOwnerByMemberIdAndGroupId( .fetchOne() ); } + + public Optional> findGroupMemberIds(final Long groupId) { + return Optional.ofNullable(jpaQueryFactory + .select(memberGroup.member.id) + .from(memberGroup) + .where(memberGroup.group.id.eq(groupId)) + .fetch()); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java index 0bf45099e..848f95b95 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -1,12 +1,15 @@ package site.timecapsulearchive.core.domain.member_group.service; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; @Service @RequiredArgsConstructor @@ -14,6 +17,7 @@ public class MemberGroupQueryService { private final GroupInviteRepository groupInviteRepository; + private final MemberGroupRepository memberGroupRepository; public Slice findGroupInvites( final Long memberId, @@ -23,4 +27,9 @@ public Slice findGroupInvites( return groupInviteRepository.findGroupInvitesSummary(memberId, size, createdAt); } + public List findGroupMemberIds(final Long groupId) { + return memberGroupRepository.findGroupMemberIds(groupId).orElseThrow( + GroupNotFoundException::new); + } + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java index e1dab1ae9..d02ff813e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java @@ -15,9 +15,10 @@ public final class TagGenerator { /** * 이메일과 소셜 타입(대문자)으로 태그를 생성한다. * - * @param email 이메일 + * @param email 이메일 * @param socialType 소셜 타입 - * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> {@code "test1234-123456GG"} + * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> + * {@code "test1234-123456GG"} */ public static String generate(final String email, final SocialType socialType) { final String randomInts = generateRandomInts(); @@ -41,9 +42,10 @@ private static String generateRandomInts() { /** * 이메일과 소셜 타입(소문자)으로 태그를 생성한다. * - * @param email 이메일 + * @param email 이메일 * @param socialType 소셜 타입 - * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> {@code "test1234-123456gg"} + * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> + * {@code "test1234-123456gg"} */ public static String lowercase(final String email, final SocialType socialType) { final String randomInts = generateRandomInts(); From 13587b43c5eed35fbd29ddb5882accb39786794c Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 30 May 2024 14:00:04 +0900 Subject: [PATCH 076/195] =?UTF-8?q?refactor=20:=20transactionTemplate=20?= =?UTF-8?q?=EB=9E=8C=EB=8B=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CapsuleSkinService.java | 11 +--- .../service/command/FriendCommandService.java | 57 ++++++++----------- .../service/command/GroupCommandService.java | 2 +- .../service/MemberGroupCommandService.java | 42 ++++++-------- 4 files changed, 45 insertions(+), 67 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java index 809d2f69e..e89f37552 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java @@ -5,9 +5,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; 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.capsuleskin.data.dto.CapsuleSkinCreateDto; import site.timecapsulearchive.core.domain.capsuleskin.data.dto.CapsuleSkinSummaryDto; @@ -59,12 +57,9 @@ public CapsuleSkinStatusResponse sendCapsuleSkinCreateMessage( if (isNotExistMotionNameAndRetarget(dto)) { CapsuleSkin capsuleSkin = capsuleSkinMapper.createDtoToEntity(dto, foundMember); - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - capsuleSkinRepository.save(capsuleSkin); - } - }); + transactionTemplate.executeWithoutResult(status -> + capsuleSkinRepository.save(capsuleSkin) + ); return CapsuleSkinStatusResponse.success(); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java index 00ada5929..255fe7a62 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java @@ -4,9 +4,7 @@ import java.util.Optional; 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.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; @@ -34,15 +32,12 @@ public void requestFriends(Long memberId, List friendIds) { final Member[] owner = new Member[1]; final List[] foundFriendIds = new List[1]; - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - owner[0] = memberRepository.findMemberById(memberId) - .orElseThrow(MemberNotFoundException::new); - foundFriendIds[0] = memberRepository.findMemberIdsByIds(friendIds); + transactionTemplate.executeWithoutResult(status -> { + owner[0] = memberRepository.findMemberById(memberId) + .orElseThrow(MemberNotFoundException::new); + foundFriendIds[0] = memberRepository.findMemberIdsByIds(friendIds); - friendInviteRepository.bulkSave(owner[0].getId(), foundFriendIds[0]); - } + friendInviteRepository.bulkSave(owner[0].getId(), foundFriendIds[0]); }); socialNotificationManager.sendFriendRequestMessages( @@ -64,12 +59,9 @@ public void requestFriend(final Long memberId, final Long friendId) { final FriendInvite createfriendInvite = FriendInvite.createOf(owner, friend); - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - friendInviteRepository.save(createfriendInvite); - } - }); + transactionTemplate.executeWithoutResult(status -> + friendInviteRepository.save(createfriendInvite) + ); socialNotificationManager.sendFriendReqMessage(owner.getNickname(), friendId); } @@ -93,31 +85,28 @@ private void validateTwoWayInvite(final Long memberId, final Long friendId) { public void acceptFriend(final Long memberId, final Long friendId) { validateFriendDuplicateId(memberId, friendId); - final String[] ownerNickname = new String[1]; - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - final List friendInvites = friendInviteRepository - .findFriendInviteWithMembersByOwnerIdAndFriendId(memberId, friendId); + final String ownerNickname = transactionTemplate.execute(status -> { + final List friendInvites = friendInviteRepository + .findFriendInviteWithMembersByOwnerIdAndFriendId(memberId, friendId); - if (friendInvites.isEmpty()) { - throw new FriendInviteNotFoundException(); - } + if (friendInvites.isEmpty()) { + throw new FriendInviteNotFoundException(); + } - final FriendInvite friendInvite = friendInvites.get(0); + final FriendInvite friendInvite = friendInvites.get(0); - final MemberFriend ownerRelation = friendInvite.ownerRelation(); - ownerNickname[0] = ownerRelation.getOwnerNickname(); + final MemberFriend ownerRelation = friendInvite.ownerRelation(); - final MemberFriend friendRelation = friendInvite.friendRelation(); + final MemberFriend friendRelation = friendInvite.friendRelation(); - memberFriendRepository.save(ownerRelation); - memberFriendRepository.save(friendRelation); - friendInvites.forEach(friendInviteRepository::delete); - } + memberFriendRepository.save(ownerRelation); + memberFriendRepository.save(friendRelation); + friendInvites.forEach(friendInviteRepository::delete); + + return ownerRelation.getOwnerNickname(); }); - socialNotificationManager.sendFriendAcceptMessage(ownerNickname[0], friendId); + socialNotificationManager.sendFriendAcceptMessage(ownerNickname, friendId); } @Transactional diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index d6c78dbd4..84571407a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -68,7 +68,7 @@ public void createGroup(final Long memberId, final GroupCreateDto dto) { * @param groupId 그룹 아이디 */ public void deleteGroup(final Long memberId, final Long groupId) { - final String groupProfilePath = transactionTemplate.execute(ignored -> { + final String groupProfilePath = transactionTemplate.execute(status -> { final Group group = groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 7596c951d..dde42ed0c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -43,23 +43,20 @@ public void inviteGroup(final Long memberId, final SendGroupRequest sendGroupReq throw new GroupMemberCountLimitException(); } - final GroupOwnerSummaryDto[] summaryDto = new GroupOwnerSummaryDto[1]; - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - summaryDto[0] = memberGroupRepository.findOwnerInMemberGroup( - sendGroupRequest.groupId(), memberId).orElseThrow(GroupNotFoundException::new); - - if (!summaryDto[0].isOwner()) { - throw new NoGroupAuthorityException(); - } - - groupInviteRepository.bulkSave(memberId, sendGroupRequest.groupId(), friendIds); + final GroupOwnerSummaryDto groupOwnerSummaryDto = transactionTemplate.execute(status -> { + GroupOwnerSummaryDto dto = memberGroupRepository.findOwnerInMemberGroup( + sendGroupRequest.groupId(), memberId).orElseThrow(GroupNotFoundException::new); + + if (dto.isOwner()) { + throw new NoGroupAuthorityException(); } + + groupInviteRepository.bulkSave(memberId, sendGroupRequest.groupId(), friendIds); + return dto; }); - socialNotificationManager.sendGroupInviteMessage(summaryDto[0].nickname(), - summaryDto[0].groupProfileUrl(), friendIds); + socialNotificationManager.sendGroupInviteMessage(groupOwnerSummaryDto.nickname(), + groupOwnerSummaryDto.groupProfileUrl(), friendIds); } @Transactional @@ -78,18 +75,15 @@ public void acceptGroupInvite(final Long memberId, final Long groupId, final Lon 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); + transactionTemplate.executeWithoutResult(status -> { + final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, targetId, memberId); - if (isDenyRequest != 1) { - throw new GroupInviteNotFoundException(); - } - - memberGroupRepository.save(MemberGroup.createGroupMember(groupMember, group)); + if (isDenyRequest != 1) { + throw new GroupInviteNotFoundException(); } + + memberGroupRepository.save(MemberGroup.createGroupMember(groupMember, group)); }); socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), targetId); From e2435284294a762843c109a2ab0c952e79620c59 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 30 May 2024 18:35:36 +0900 Subject: [PATCH 077/195] =?UTF-8?q?refactor=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=88=98=EB=9D=BD=20=EB=A0=88=EB=94=94?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EC=82=B0=20=EB=9D=BD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/core/build.gradle | 1 + .../repository/GroupQueryRepository.java | 4 ++ .../repository/GroupQueryRepositoryImpl.java | 49 +++++++++----- .../service/MemberGroupCommandService.java | 12 +++- .../config/{ => redis}/RedisConfig.java | 2 +- .../global/config/redis/RedisProperties.java | 11 ++++ .../global/config/redis/RedissonConfig.java | 25 ++++++++ .../global/config/redis/RedissonLock.java | 17 +++++ .../config/redis/RedissonLockAspect.java | 64 +++++++++++++++++++ .../core/global/error/ErrorCode.java | 4 ++ .../error/exception/RedisLockException.java | 10 +++ .../core/global/util/RedisLockSpELParser.java | 19 ++++++ 12 files changed, 198 insertions(+), 20 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/global/config/{ => redis}/RedisConfig.java (97%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedisProperties.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/RedisLockException.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/util/RedisLockSpELParser.java diff --git a/backend/core/build.gradle b/backend/core/build.gradle index 7520ee105..23020ca25 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -50,6 +50,7 @@ dependencies { //redis implementation 'org.springframework.data:spring-data-redis' implementation 'io.lettuce:lettuce-core:6.3.0.RELEASE' + implementation 'org.redisson:redisson-spring-boot-starter:3.30.0' //RabbitMQ implementation 'org.springframework.boot:spring-boot-starter-amqp' diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index 3affaae9b..d386aad63 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -1,7 +1,9 @@ package site.timecapsulearchive.core.domain.group.repository; import java.time.ZonedDateTime; +import java.util.List; import java.util.Optional; +import java.util.OptionalLong; import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; @@ -17,4 +19,6 @@ Slice findGroupsSlice( Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId); + + Optional getTotalGroupMemberCount(Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index 398d1d64f..710d9e0fa 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -55,11 +55,29 @@ public Slice findGroupsSlice( .fetch(); final List groupIds = groups.stream().map(GroupSummaryDto::id).toList(); + final List groupOwnerProfileUrls = getGroupOwnerProfileUrls(groupIds); + final List totalGroupMemberCount = getTotalGroupMemberCount(groupIds); - final List groupOwnerProfileUrls = jpaQueryFactory - .select( - member.profileUrl + final boolean hasNext = groups.size() > size; + if (hasNext) { + groups.remove(size); + } + + final List dtos = IntStream.range(0, groups.size()) + .mapToObj(i -> new FinalGroupSummaryDto( + groups.get(i), + groupOwnerProfileUrls.get(i), + totalGroupMemberCount.get(i) + ) ) + .toList(); + + return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); + } + + private List getGroupOwnerProfileUrls(final List groupIds) { + return jpaQueryFactory + .select(member.profileUrl) .from(memberGroup) .join(memberGroup.group, group) .join(memberGroup.member, member) @@ -67,26 +85,16 @@ public Slice findGroupsSlice( .and(memberGroup.isOwner.eq(true))) .orderBy(group.id.desc()) .fetch(); + } - final List totalGroupMemberCount = jpaQueryFactory + private List getTotalGroupMemberCount(final List groupIds) { + return jpaQueryFactory .select(memberGroup.count()) .from(memberGroup) .where(memberGroup.group.id.in(groupIds)) .groupBy(memberGroup.group.id) .orderBy(memberGroup.group.id.desc()) .fetch(); - - final boolean hasNext = groups.size() > size; - if (hasNext) { - groups.remove(size); - } - - List dtos = IntStream.range(0, groups.size()) - .mapToObj(i -> new FinalGroupSummaryDto(groups.get(i), - groupOwnerProfileUrls.get(i), totalGroupMemberCount.get(i))) - .toList(); - - return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); } /** @@ -153,4 +161,13 @@ public Optional findGroupDetailByGroupIdAndMemberId(final Long g ) ); } + + public Optional getTotalGroupMemberCount(final Long groupId) { + return Optional.ofNullable(jpaQueryFactory + .select(memberGroup.count()) + .from(memberGroup) + .where(memberGroup.group.id.eq(groupId)) + .fetchOne() + ); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index dde42ed0c..8601d4e4a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -3,9 +3,7 @@ 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.entity.Group; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; @@ -23,6 +21,7 @@ import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.global.config.redis.RedissonLock; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Service @@ -69,7 +68,15 @@ public void rejectRequestGroup(final Long memberId, final Long groupId, final Lo } } + @RedissonLock(value = "#groupId + ':' + #targetId") public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { + final Long totalGroupMemberCount = groupRepository.getTotalGroupMemberCount(groupId) + .orElseThrow(GroupNotFoundException::new); + + if (totalGroupMemberCount == 30) { + throw new GroupMemberCountLimitException(); + } + final Member groupMember = memberRepository.findMemberById(memberId) .orElseThrow(MemberNotFoundException::new); final Group group = groupRepository.findGroupById(groupId) @@ -78,7 +85,6 @@ public void acceptGroupInvite(final Long memberId, final Long groupId, final Lon transactionTemplate.executeWithoutResult(status -> { final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( groupId, targetId, memberId); - if (isDenyRequest != 1) { throw new GroupInviteNotFoundException(); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/RedisConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedisConfig.java similarity index 97% rename from backend/core/src/main/java/site/timecapsulearchive/core/global/config/RedisConfig.java rename to backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedisConfig.java index c1a31b2a3..992541d4f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/RedisConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedisConfig.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.global.config; +package site.timecapsulearchive.core.global.config.redis; import jakarta.persistence.EntityManagerFactory; import org.springframework.context.annotation.Bean; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedisProperties.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedisProperties.java new file mode 100644 index 000000000..05700ef07 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedisProperties.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.global.config.redis; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.data.redis") +public record RedisProperties( + String host, + int port +) { + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java new file mode 100644 index 000000000..1ea17ae8f --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java @@ -0,0 +1,25 @@ +package site.timecapsulearchive.core.global.config.redis; + +import lombok.RequiredArgsConstructor; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class RedissonConfig { + + private static final String REDISSON_HOST_PREFIX = "redis://"; + private final RedisProperties redisProperties; + + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + config.useSingleServer().setAddress( + REDISSON_HOST_PREFIX + redisProperties.host() + ":" + redisProperties.port()); + return Redisson.create(config); + } +} + diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java new file mode 100644 index 000000000..48ea42178 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java @@ -0,0 +1,17 @@ +package site.timecapsulearchive.core.global.config.redis; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RedissonLock { + + String value(); + long waitTime() default 5000L; + long leaseTime() default 3000L; +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java new file mode 100644 index 000000000..a3a48d822 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java @@ -0,0 +1,64 @@ +package site.timecapsulearchive.core.global.config.redis; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.lang.reflect.Method; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.RedisLockException; +import site.timecapsulearchive.core.global.util.RedisLockSpELParser; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class RedissonLockAspect { + + private final RedissonClient redissonClient; + + @Around("@annotation(RedissonLock)") + public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + RedissonLock redissonLock = method.getAnnotation(RedissonLock.class); + + String lockKey = + method.getName() + "/" + RedisLockSpELParser.getLockKey(signature.getParameterNames(), + joinPoint.getArgs(), redissonLock.value()); + + long waitTime = redissonLock.waitTime(); + long leaseTime = redissonLock.leaseTime(); + + RLock lock = redissonClient.getLock(lockKey); + boolean isLocked = false; + + try { + isLocked = lock.tryLock(waitTime, leaseTime, MILLISECONDS); + if (isLocked) { + log.info("락을 얻는데 성공하였습니다. (락 키 : {})", lockKey); + return joinPoint.proceed(); + } else { + log.warn("락을 얻는데 실패하였습니다. (락 키 : {})", lockKey); + throw new RedisLockException(ErrorCode.REDIS_FAILED_GET_LOCK_ERROR); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("락을 얻는데 인터럽트가 발생하였습니다. (락 키 : {})", lockKey); + throw new RedisLockException(ErrorCode.REDIS_INTERRUPT_ERROR); + } finally { + if (isLocked) { + lock.unlock(); + log.info("락을 해제하는데 성공하였습니다. (락 키 : {})", lockKey); + } + } + } + +} 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 2f97178f9..13213a448 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 @@ -36,6 +36,10 @@ public enum ErrorCode { //외부 API EXTERNAL_API_ERROR(500, "EXTERNAL-001", "외부 api 호출에 실패했습니다. 잠시 후 요청해주세요."), + //Redis 분산 락 + REDIS_FAILED_GET_LOCK_ERROR(403, "LOCK-001", "분산 락을 얻는데 실패 하였습니다."), + REDIS_INTERRUPT_ERROR(403, "LOCK-002", "락을 얻는데 인터럽트가 발생 하였습니다."), + //member LOGIN_ON_NOT_VERIFIED_ERROR(400, "MEMBER-001", "인증되지 않은 사용자로 로그인을 시도했습니다."), diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/RedisLockException.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/RedisLockException.java new file mode 100644 index 000000000..35c219f8a --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/RedisLockException.java @@ -0,0 +1,10 @@ +package site.timecapsulearchive.core.global.error.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; + +public class RedisLockException extends RuntimeException { + + public RedisLockException(ErrorCode errorCode) { + super(errorCode.getMessage()); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/util/RedisLockSpELParser.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/RedisLockSpELParser.java new file mode 100644 index 000000000..8d5f8c9a7 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/RedisLockSpELParser.java @@ -0,0 +1,19 @@ +package site.timecapsulearchive.core.global.util; + +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +public final class RedisLockSpELParser { + + public static Object getLockKey(String[] parameterNames, Object[] args, String key) { + SpelExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + + for (int i = 0; i < parameterNames.length; i++) { + context.setVariable(parameterNames[i], args[i]); + } + + return parser.parseExpression(key).getValue(context, Object.class); + } + +} From 728ca2df663d3ab0a86554c29f72301f227004ae Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 30 May 2024 20:21:45 +0900 Subject: [PATCH 078/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=88=98=EB=9D=BD=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=ED=95=84=EB=93=9C,=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/command/MemberGroupCommandApi.java | 5 +--- .../MemberGroupCommandApiController.java | 7 ++--- .../MemberGroupQueryRepository.java | 2 ++ .../MemberGroupQueryRepositoryImpl.java | 9 ++++++ .../service/MemberGroupCommandService.java | 12 ++++---- .../config/redis/RedissonLockAspect.java | 2 +- .../MemberGroupCommandServiceTest.java | 30 +++++++++++++++---- 7 files changed, 47 insertions(+), 20 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java index e193e273e..9b9d85e67 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java @@ -104,10 +104,7 @@ ResponseEntity> acceptGroupInvitation( Long memberId, @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) - Long groupId, - - @Parameter(in = ParameterIn.PATH, description = "대상 회원 아이디", required = true) - Long targetId + Long groupId ); @Operation( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java index 6af6bce41..cc8184147 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java @@ -47,14 +47,13 @@ public ResponseEntity> inviteGroup( ); } - @PostMapping(value = "/{group_id}/member/{target_id}/accept") + @PostMapping(value = "/{group_id}/accept") @Override public ResponseEntity> acceptGroupInvitation( @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId, - @PathVariable("target_id") final Long targetId + @PathVariable("group_id") final Long groupId ) { - memberGroupCommandService.acceptGroupInvite(memberId, groupId, targetId); + memberGroupCommandService.acceptGroupInvite(memberId, groupId); return ResponseEntity.ok( ApiSpec.empty( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java index 81bcb5d2e..4ec8de458 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -11,4 +11,6 @@ public interface MemberGroupQueryRepository { Optional findIsOwnerByMemberIdAndGroupId(Long groupOwnerId, Long groupId); Optional> findGroupMemberIds(Long groupId); + + Optional findGroupOwnerId(Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 22d9a6035..d2531cdbb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -58,4 +58,13 @@ public Optional> findGroupMemberIds(final Long groupId) { .where(memberGroup.group.id.eq(groupId)) .fetch()); } + + @Override + public Optional findGroupOwnerId(final Long groupId) { + return Optional.ofNullable(jpaQueryFactory + .select(memberGroup.member.id) + .from(memberGroup) + .where(memberGroup.group.id.eq(groupId).and(memberGroup.isOwner.eq(true))) + .fetchOne()); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 8601d4e4a..0f161e03b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -46,7 +46,7 @@ public void inviteGroup(final Long memberId, final SendGroupRequest sendGroupReq GroupOwnerSummaryDto dto = memberGroupRepository.findOwnerInMemberGroup( sendGroupRequest.groupId(), memberId).orElseThrow(GroupNotFoundException::new); - if (dto.isOwner()) { + if (!dto.isOwner()) { throw new NoGroupAuthorityException(); } @@ -68,8 +68,8 @@ public void rejectRequestGroup(final Long memberId, final Long groupId, final Lo } } - @RedissonLock(value = "#groupId + ':' + #targetId") - public void acceptGroupInvite(final Long memberId, final Long groupId, final Long targetId) { + @RedissonLock(value = "#groupId") + public void acceptGroupInvite(final Long memberId, final Long groupId) { final Long totalGroupMemberCount = groupRepository.getTotalGroupMemberCount(groupId) .orElseThrow(GroupNotFoundException::new); @@ -81,10 +81,12 @@ public void acceptGroupInvite(final Long memberId, final Long groupId, final Lon .orElseThrow(MemberNotFoundException::new); final Group group = groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); + final Long groupOwnerId = memberGroupRepository.findGroupOwnerId(groupId) + .orElseThrow(GroupNotFoundException::new); transactionTemplate.executeWithoutResult(status -> { final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId); + groupId, groupOwnerId, memberId); if (isDenyRequest != 1) { throw new GroupInviteNotFoundException(); } @@ -92,7 +94,7 @@ public void acceptGroupInvite(final Long memberId, final Long groupId, final Lon memberGroupRepository.save(MemberGroup.createGroupMember(groupMember, group)); }); - socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), targetId); + socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), groupOwnerId); } /** diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java index a3a48d822..db5a32ab5 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java @@ -31,7 +31,7 @@ public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable { RedissonLock redissonLock = method.getAnnotation(RedissonLock.class); String lockKey = - method.getName() + "/" + RedisLockSpELParser.getLockKey(signature.getParameterNames(), + method.getName() + ":" + RedisLockSpELParser.getLockKey(signature.getParameterNames(), joinPoint.getArgs(), redissonLock.value()); long waitTime = redissonLock.waitTime(); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 58cfe0eee..ed9d8ba60 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -172,17 +172,19 @@ class MemberGroupCommandServiceTest { //given Long memberId = 1L; Long groupId = 1L; - Long targetId = 2L; Member groupMember = MemberFixture.member(1); + given(groupRepository.getTotalGroupMemberCount(groupId)).willReturn(Optional.of(10L)); given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); given(groupRepository.findGroupById(groupId)).willReturn( Optional.of(GroupFixture.group())); + given(memberGroupRepository.findGroupOwnerId(groupId)).willReturn(Optional.of(2L)); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId)).willReturn(1); + groupId, 2L, memberId)).willReturn(1); //when - groupMemberCommandService.acceptGroupInvite(memberId, groupId, targetId); + groupMemberCommandService.acceptGroupInvite(memberId, groupId); //then verify(socialNotificationManager, times(1)).sendGroupAcceptMessage(anyString(), anyLong()); @@ -193,23 +195,39 @@ class MemberGroupCommandServiceTest { //given Long memberId = 1L; Long groupId = 1L; - Long targetId = 2L; Member groupMember = MemberFixture.member(1); + given(groupRepository.getTotalGroupMemberCount(groupId)).willReturn(Optional.of(10L)); given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); given(groupRepository.findGroupById(groupId)).willReturn( Optional.of(GroupFixture.group())); + given(memberGroupRepository.findGroupOwnerId(groupId)).willReturn(Optional.of(2L)); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, targetId, memberId)).willReturn(0); + groupId, 2L, memberId)).willReturn(0); //when //then assertThatThrownBy( - () -> groupMemberCommandService.acceptGroupInvite(memberId, groupId, targetId)) + () -> groupMemberCommandService.acceptGroupInvite(memberId, groupId)) .isInstanceOf(GroupInviteNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); } + @Test + void 그룹원은_그룹초대를_수락할_때_그룹초대_인원이_이미_최대_인원이면_예외가_발생한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + + given(groupRepository.getTotalGroupMemberCount(groupId)).willReturn(Optional.of(30L)); + + assertThatThrownBy( + () -> groupMemberCommandService.acceptGroupInvite(memberId, groupId)) + .isInstanceOf(GroupMemberCountLimitException.class) + .hasMessageContaining(ErrorCode.GROUP_MEMBER_COUNT_LIMIT_ERROR.getMessage()); + } + @Test void 그룹장인_사용자가_그룹_탈퇴를_시도하면_예외가_발생한다() { From e3cb97849ba2a323913e2dfc5cd78f4133ebca6f Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 31 May 2024 02:30:42 +0900 Subject: [PATCH 079/195] =?UTF-8?q?test=20:=20=EB=A0=88=EB=94=94=EC=8A=A4?= =?UTF-8?q?=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EB=B6=84=EC=82=B0=EB=9D=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/core/build.gradle | 1 + .../core/common/RedissonTest.java | 8 ++ .../core/common/RedissonTestContainer.java | 26 ++++ .../infrastructure/RedisConcurrencyTest.java | 119 ++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTest.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTestContainer.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java diff --git a/backend/core/build.gradle b/backend/core/build.gradle index 23020ca25..196b0558a 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -86,6 +86,7 @@ dependencies { testImplementation 'org.testcontainers:testcontainers:1.19.1' testImplementation 'org.testcontainers:junit-jupiter:1.19.1' testImplementation 'org.testcontainers:mysql:1.19.1' + testImplementation 'com.redis:testcontainers-redis:2.2.2' } tasks.named('test') { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTest.java new file mode 100644 index 000000000..6f7f38444 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTest.java @@ -0,0 +1,8 @@ +package site.timecapsulearchive.core.common; + +import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; + +@DataRedisTest +public abstract class RedissonTest extends RedissonTestContainer { + +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTestContainer.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTestContainer.java new file mode 100644 index 000000000..8d578336d --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/RedissonTestContainer.java @@ -0,0 +1,26 @@ +package site.timecapsulearchive.core.common; + +import com.redis.testcontainers.RedisContainer; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.utility.DockerImageName; + +public abstract class RedissonTestContainer { + + private static final String REDIS_IMAGE = "redis:alpine"; + private static final int REDIS_PORT = 6379; + private static final RedisContainer REDIS_CONTAINER; + + static { + REDIS_CONTAINER = new RedisContainer( + DockerImageName.parse(REDIS_IMAGE)).withExposedPorts(REDIS_PORT); + REDIS_CONTAINER.start(); + } + + @DynamicPropertySource + private static void registerRedisProperties(DynamicPropertyRegistry registry) { + registry.add("spring.data.redis.host", REDIS_CONTAINER::getHost); + registry.add("spring.data.redis.port", () -> REDIS_CONTAINER.getMappedPort(REDIS_PORT) + .toString()); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java new file mode 100644 index 000000000..16df0a9f2 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java @@ -0,0 +1,119 @@ +package site.timecapsulearchive.core.infrastructure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.TestConstructor; +import org.springframework.test.context.TestConstructor.AutowireMode; +import site.timecapsulearchive.core.common.RedissonTest; +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.domain.group.repository.GroupRepository; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member_group.service.MemberGroupCommandService; +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.RedisLockException; +import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; + +@TestConstructor(autowireMode = AutowireMode.ALL) +class RedisConcurrencyTest extends RedissonTest { + + private static final int MAX_THREADS_COUNT = 10; + + 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 MemberGroupCommandService groupMemberCommandService = spy( + new MemberGroupCommandService( + memberRepository, + groupRepository, + memberGroupRepository, + groupInviteRepository, + TestTransactionTemplate.spied(), + mock(SocialNotificationManager.class)) + ); + + + @Test + void 사용자는_그룹_초대_요청을_수락할_때_레디스_분산락을_통해_동기적으로_처리한다() throws InterruptedException { + //given + ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREADS_COUNT); + CountDownLatch latch = new CountDownLatch(MAX_THREADS_COUNT); + AtomicInteger countCheck = new AtomicInteger(MAX_THREADS_COUNT); + + Long memberId = 1L; + Long groupId = 1L; + + Member groupMember = MemberFixture.member(1); + + given(groupRepository.getTotalGroupMemberCount(groupId)) + .willReturn(Optional.of(10L)); + given(memberRepository.findMemberById(memberId)) + .willReturn(Optional.of(groupMember)); + given(groupRepository.findGroupById(groupId)).willReturn( + Optional.of(GroupFixture.group())); + given(memberGroupRepository.findGroupOwnerId(groupId)) + .willReturn(Optional.of(2L)); + + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, 2L, memberId)).willReturn(1); + + //when + for (int i = 0; i < MAX_THREADS_COUNT; i++) { + executorService.submit(() -> { + try { + groupMemberCommandService.acceptGroupInvite(memberId, groupId); + } finally { + latch.countDown(); + int expectedCount = countCheck.decrementAndGet(); + assertThat(expectedCount).isEqualTo(latch.getCount()); + } + }); + } + + latch.await(); + executorService.shutdown(); + + //then + verify(groupInviteRepository, + times(MAX_THREADS_COUNT)).deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + anyLong(), anyLong(), anyLong()); + verify(memberGroupRepository, times(MAX_THREADS_COUNT)).save(any()); + } + + @Test + void 사용자는_그룹_초대_요청을_수락할_때_락을_얻지_못하면_예외가_발생한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + + willThrow(new RedisLockException(ErrorCode.REDIS_FAILED_GET_LOCK_ERROR)) + .given(groupMemberCommandService).acceptGroupInvite(memberId, groupId); + + //when + //then + assertThatThrownBy(() -> groupMemberCommandService.acceptGroupInvite(memberId, groupId)) + .isInstanceOf(RedisLockException.class) + .hasMessage(ErrorCode.REDIS_FAILED_GET_LOCK_ERROR.getMessage()); + } +} From cf174a497418ef2948c9f8e1b47fdcc2e8c0ec5f Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 31 May 2024 23:17:43 +0900 Subject: [PATCH 080/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=EC=9D=98?= =?UTF-8?q?=20=EA=B7=B8=EB=A3=B9=EC=9B=90=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- .../domain/group/api/query/GroupQueryApi.java | 22 ++++++++++++++- .../api/query/GroupQueryApiController.java | 28 ++++++++++++++++++- .../group/data/dto/GroupDetailTotalDto.java | 8 +++--- .../domain/group/data/dto/GroupMemberDto.java | 12 ++++++++ .../data/dto/GroupMemberWithRelationDto.java | 9 +++--- .../data/response/GroupDetailResponse.java | 2 +- .../response/GroupMemberDetailResponse.java | 21 -------------- .../response/GroupMemberInfoResponse.java | 26 +++++++++++++++++ .../response/GroupMemberInfosResponse.java | 27 ++++++++++++++++++ ...a => GroupMemberWithRelationResponse.java} | 2 +- .../repository/GroupQueryRepository.java | 6 +++- .../repository/GroupQueryRepositoryImpl.java | 21 ++++++++++++++ .../service/query/GroupQueryService.java | 7 +++++ 14 files changed, 158 insertions(+), 35 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberDetailResponse.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfoResponse.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfosResponse.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/{GroupMemberResponse.java => GroupMemberWithRelationResponse.java} (92%) diff --git a/.gitignore b/.gitignore index 91cd78597..51e94bb56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea -data \ No newline at end of file +backend/data \ No newline at end of file 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 index 6c67826dc..f3baaae10 100644 --- 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 @@ -10,6 +10,7 @@ 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.GroupMemberInfosResponse; import site.timecapsulearchive.core.domain.group.data.response.GroupsSliceResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; @@ -38,7 +39,7 @@ public interface GroupQueryApi { ResponseEntity> findGroupDetailById( Long memberId, - @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true, schema = @Schema()) + @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true) Long groupId ); @@ -63,4 +64,23 @@ ResponseEntity> findGroups( @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) ZonedDateTime createdAt ); + + @Operation( + summary = "그룹에 대한 그룹 멤버 조회", + description = "그룹의 그룹원들 정보를 조회한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findGroupMemberInfos( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true) + Long groupId + ); } 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 index 6e7e200f8..dbb3a90d6 100644 --- 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 @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.group.api.query; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.http.ResponseEntity; @@ -12,7 +13,9 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; +import site.timecapsulearchive.core.domain.group.data.response.GroupMemberInfosResponse; 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; @@ -28,7 +31,7 @@ public class GroupQueryApiController implements GroupQueryApi { private final S3PreSignedUrlManager s3PreSignedUrlManager; @GetMapping( - value = "/{group_id}", + value = "/{group_id}/detail", produces = {"application/json"} ) @Override @@ -72,4 +75,27 @@ public ResponseEntity> findGroups( ) ); } + + @GetMapping( + value = "/{group_id}/members", + produces = {"application/json"} + ) + @Override + public ResponseEntity> findGroupMemberInfos( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId + ) { + final List groupMemberDtos = groupQueryService.findGroupMemberInfos( + memberId, groupId); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + GroupMemberInfosResponse.createOf( + groupMemberDtos, + s3PreSignedUrlManager::getS3PreSignedUrlForGet + ) + ) + ); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java index 55209b7c3..1770c5b8f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.function.Function; import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; -import site.timecapsulearchive.core.domain.group.data.response.GroupMemberResponse; +import site.timecapsulearchive.core.domain.group.data.response.GroupMemberWithRelationResponse; public record GroupDetailTotalDto( String groupName, @@ -38,8 +38,8 @@ public static GroupDetailTotalDto as( } public GroupDetailResponse toResponse(final Function singlePreSignUrlFunction) { - List members = this.members.stream() - .map(GroupMemberWithRelationDto::toResponse) + List groupMemberResponses = members.stream() + .map(member -> member.toResponse(singlePreSignUrlFunction)) .toList(); return GroupDetailResponse.builder() @@ -49,7 +49,7 @@ public GroupDetailResponse toResponse(final Function singlePreSi .createdAt(createdAt) .groupCapsuleTotalCount(groupCapsuleTotalCount) .canGroupEdit(canGroupEdit) - .members(members) + .members(groupMemberResponses) .build(); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java index ab9ae4506..b1c17a3e3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java @@ -1,6 +1,8 @@ package site.timecapsulearchive.core.domain.group.data.dto; import java.util.List; +import java.util.function.Function; +import site.timecapsulearchive.core.domain.group.data.response.GroupMemberInfoResponse; public record GroupMemberDto( Long memberId, @@ -20,4 +22,14 @@ public GroupMemberWithRelationDto toRelationDto(final List friendIds) { .isFriend(friendIds.contains(memberId)) .build(); } + + public GroupMemberInfoResponse toInfoResponse(final Function singlePreSignUrlFunction) { + return GroupMemberInfoResponse.builder() + .memberId(memberId) + .profileUrl(singlePreSignUrlFunction.apply(profileUrl)) + .nickname(nickname) + .tag(tag) + .isOwner(isOwner) + .build(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java index 29f3dbf12..66d116c40 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java @@ -1,7 +1,8 @@ package site.timecapsulearchive.core.domain.group.data.dto; +import java.util.function.Function; import lombok.Builder; -import site.timecapsulearchive.core.domain.group.data.response.GroupMemberResponse; +import site.timecapsulearchive.core.domain.group.data.response.GroupMemberWithRelationResponse; @Builder public record GroupMemberWithRelationDto( @@ -13,10 +14,10 @@ public record GroupMemberWithRelationDto( Boolean isFriend ) { - public GroupMemberResponse toResponse() { - return GroupMemberResponse.builder() + public GroupMemberWithRelationResponse toResponse(final Function singlePreSignUrlFunction) { + return GroupMemberWithRelationResponse.builder() .memberId(memberId) - .profileUrl(profileUrl) + .profileUrl(singlePreSignUrlFunction.apply(profileUrl)) .nickname(nickname) .tag(tag) .isOwner(isOwner) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java index ed3852aa8..e6fafea82 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupDetailResponse.java @@ -29,7 +29,7 @@ public record GroupDetailResponse( Boolean canGroupEdit, @Schema(description = "그룹원 리스트") - List members + List members ) { public GroupDetailResponse { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberDetailResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberDetailResponse.java deleted file mode 100644 index 1d4f72fb9..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberDetailResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package site.timecapsulearchive.core.domain.group.data.response; - -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "그룹원 상세 정보") -public record GroupMemberDetailResponse( - - @Schema(description = "그룹원 아이디") - Long id, - - @Schema(description = "그룹원 이름") - String nickname, - - @Schema(description = "프로필 url") - String profileUrl, - - @Schema(description = "사용자와 친구 여부") - Boolean isFriend -) { - -} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfoResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfoResponse.java new file mode 100644 index 000000000..de4ed215f --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfoResponse.java @@ -0,0 +1,26 @@ +package site.timecapsulearchive.core.domain.group.data.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Builder +@Schema(description = "그룹원 정보") +public record GroupMemberInfoResponse( + + @Schema(description = "그룹원 아이디") + Long memberId, + + @Schema(description = "그룹원 프로필 url") + String profileUrl, + + @Schema(description = "그룹원 닉네임") + String nickname, + + @Schema(description = "그룹원 태그") + String tag, + + @Schema(description = "그룹장 여부") + Boolean isOwner +) { + +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfosResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfosResponse.java new file mode 100644 index 000000000..93b54068e --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberInfosResponse.java @@ -0,0 +1,27 @@ +package site.timecapsulearchive.core.domain.group.data.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import java.util.function.Function; +import lombok.Builder; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; + +@Builder +@Schema(description = "그룹원들 정보 리스트") +public record GroupMemberInfosResponse( + + @Schema(description = "그룹원들 정보") + List groupMemberResponses +) { + + public static GroupMemberInfosResponse createOf( + final List groupMemberDtos, + final Function singlePreSignUrlFunction + ) { + List groupMemberResponses = groupMemberDtos.stream() + .map(dto -> dto.toInfoResponse(singlePreSignUrlFunction)) + .toList(); + + return new GroupMemberInfosResponse(groupMemberResponses); + } +} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberWithRelationResponse.java similarity index 92% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberResponse.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberWithRelationResponse.java index 440ff6e3d..e4feca504 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupMemberWithRelationResponse.java @@ -5,7 +5,7 @@ @Builder @Schema(description = "그룹원 정보") -public record GroupMemberResponse( +public record GroupMemberWithRelationResponse( @Schema(description = "그룹원 아이디") Long memberId, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index d386aad63..bffb7893f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -3,10 +3,11 @@ import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; -import java.util.OptionalLong; import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberWithRelationDto; public interface GroupQueryRepository { @@ -21,4 +22,7 @@ Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId); Optional getTotalGroupMemberCount(Long groupId); + + List findGroupMemberInfos(Long memberId, Long groupId); + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index 710d9e0fa..e3df96623 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -170,4 +170,25 @@ public Optional getTotalGroupMemberCount(final Long groupId) { .fetchOne() ); } + + @Override + public List findGroupMemberInfos( + final Long memberId, + final Long groupId + ) { + return jpaQueryFactory + .select(Projections.constructor( + GroupMemberDto.class, + member.id, + member.profileUrl, + member.nickname, + member.tag, + memberGroup.isOwner + ) + ) + .from(memberGroup) + .join(memberGroup.member, member) + .where(memberGroup.group.id.eq(groupId)) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index c1f996772..3cb6eba43 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -51,4 +51,11 @@ public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final L return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, friendIds); } + + public List findGroupMemberInfos( + final Long memberId, + final Long groupId + ) { + return groupRepository.findGroupMemberInfos(memberId, groupId); + } } From 2464e3112fa51fe4553764c25bb6190204ab76f6 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Fri, 31 May 2024 23:18:17 +0900 Subject: [PATCH 081/195] =?UTF-8?q?chore=20:=20=EC=9E=90=EB=B0=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=ED=98=95=EC=8B=9D=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/capsuleskin/service/CapsuleSkinService.java | 2 +- .../core/domain/group/api/query/GroupQueryApi.java | 1 - .../core/domain/group/data/dto/GroupMemberDto.java | 3 ++- .../core/domain/group/data/dto/GroupMemberWithRelationDto.java | 3 ++- .../core/domain/group/repository/GroupQueryRepository.java | 1 - .../core/global/config/redis/RedissonLock.java | 2 ++ 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java index e89f37552..655552796 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsuleskin/service/CapsuleSkinService.java @@ -58,7 +58,7 @@ public CapsuleSkinStatusResponse sendCapsuleSkinCreateMessage( CapsuleSkin capsuleSkin = capsuleSkinMapper.createDtoToEntity(dto, foundMember); transactionTemplate.executeWithoutResult(status -> - capsuleSkinRepository.save(capsuleSkin) + capsuleSkinRepository.save(capsuleSkin) ); return CapsuleSkinStatusResponse.success(); } 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 index f3baaae10..cba9c0e4c 100644 --- 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 @@ -3,7 +3,6 @@ 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; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java index b1c17a3e3..2701cbf21 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberDto.java @@ -23,7 +23,8 @@ public GroupMemberWithRelationDto toRelationDto(final List friendIds) { .build(); } - public GroupMemberInfoResponse toInfoResponse(final Function singlePreSignUrlFunction) { + public GroupMemberInfoResponse toInfoResponse( + final Function singlePreSignUrlFunction) { return GroupMemberInfoResponse.builder() .memberId(memberId) .profileUrl(singlePreSignUrlFunction.apply(profileUrl)) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java index 66d116c40..dbe86a084 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java @@ -14,7 +14,8 @@ public record GroupMemberWithRelationDto( Boolean isFriend ) { - public GroupMemberWithRelationResponse toResponse(final Function singlePreSignUrlFunction) { + public GroupMemberWithRelationResponse toResponse( + final Function singlePreSignUrlFunction) { return GroupMemberWithRelationResponse.builder() .memberId(memberId) .profileUrl(singlePreSignUrlFunction.apply(profileUrl)) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index bffb7893f..42a00ef9a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -7,7 +7,6 @@ import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberWithRelationDto; public interface GroupQueryRepository { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java index 48ea42178..548630007 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLock.java @@ -12,6 +12,8 @@ public @interface RedissonLock { String value(); + long waitTime() default 5000L; + long leaseTime() default 3000L; } From ef809e4695d83fc4307c26e2c259392f9e2ae955 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 16:06:39 +0900 Subject: [PATCH 082/195] =?UTF-8?q?feat=20:=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=B3=B4=EB=82=B8=EA=B1=B0,=20=EB=B0=9B=EC=9D=80=EA=B1=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 API에서 함수 이름 변경 - 친구 요청 보낸 목록 조회 추가 - 쿼리 오류 수정(createdAt.lt -> createdAt.loe) - 테스트 추가 --- .gitignore | 2 +- .../friend/api/query/FriendQueryApi.java | 33 ++++++++++++++++--- .../api/query/FriendQueryApiController.java | 33 +++++++++++++++---- .../response/FriendRequestsSliceResponse.java | 26 --------------- .../MemberFriendQueryRepository.java | 8 ++++- .../MemberFriendQueryRepositoryImpl.java | 33 ++++++++++++++++--- .../service/query/FriendQueryService.java | 12 +++++-- 7 files changed, 103 insertions(+), 44 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendRequestsSliceResponse.java diff --git a/.gitignore b/.gitignore index 91cd78597..51e94bb56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea -data \ No newline at end of file +backend/data \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java index 0cd05496a..bf4fa86b9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java @@ -9,7 +9,6 @@ import java.time.ZonedDateTime; import org.springframework.http.ResponseEntity; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; -import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; @@ -65,8 +64,34 @@ ResponseEntity> findFriendsBeforeGroupInvite( ); @Operation( - summary = "소셜 친구 요청 목록 조회", - description = "사용자의 소셜 친구 요청 목록을 보여준다. 수락 대기 중인 요청만 해당한다.", + summary = "소셜 친구 요청 받은 목록 조회", + description = """ + 사용자가 소셜 친구 요청을 받은 목록을 보여준다. +
+ 수락 대기 중인 요청만 해당한다. + """, + security = {@SecurityRequirement(name = "user_token")}, + tags = {"friend"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findFriendReceptionInvites( + 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 = {"friend"} ) @@ -76,7 +101,7 @@ ResponseEntity> findFriendsBeforeGroupInvite( description = "ok" ) }) - ResponseEntity> findFriendRequests( + ResponseEntity> findFriendSendingInvites( Long memberId, @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java index f9856fb3b..46077f0c9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java @@ -18,7 +18,6 @@ import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest; -import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.FriendsSliceResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchFriendsResponse; import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; @@ -77,23 +76,45 @@ public ResponseEntity> findFriendsBeforeGroupInvit } @GetMapping( - value = "/requests", + value = "/reception-invites", produces = {"application/json"} ) @Override - public ResponseEntity> findFriendRequests( + public ResponseEntity> findFriendReceptionInvites( @AuthenticationPrincipal final Long memberId, @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice friendRequestsSlice = friendQueryService.findFriendRequestsSlice( + final Slice friendReceptionInvitesSlice = friendQueryService.findFriendReceptionInvitesSlice( memberId, size, createdAt); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - FriendRequestsSliceResponse.createOf(friendRequestsSlice.getContent(), - friendRequestsSlice.hasNext()) + FriendsSliceResponse.createOf(friendReceptionInvitesSlice.getContent(), + friendReceptionInvitesSlice.hasNext()) + ) + ); + } + + @GetMapping( + value = "/sending-invites", + produces = {"application/json"} + ) + @Override + public ResponseEntity> findFriendSendingInvites( + @AuthenticationPrincipal final Long memberId, + @RequestParam(defaultValue = "20", value = "size") final int size, + @RequestParam(value = "created_at") final ZonedDateTime createdAt + ) { + final Slice friendSendingInvitesSlice = friendQueryService.findFriendSendingInvitesSlice( + memberId, size, createdAt); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + FriendsSliceResponse.createOf(friendSendingInvitesSlice.getContent(), + friendSendingInvitesSlice.hasNext()) ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendRequestsSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendRequestsSliceResponse.java deleted file mode 100644 index 731b823e5..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/response/FriendRequestsSliceResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package site.timecapsulearchive.core.domain.friend.data.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; -import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; - -@Schema(description = "친구 요청 리스트") -public record FriendRequestsSliceResponse( - @Schema(description = "친구 요약 정보 리스트") - List friends, - - @Schema(description = "다음 페이지 유무") - Boolean hasNext -) { - - public static FriendRequestsSliceResponse createOf( - List content, - boolean hasNext - ) { - List friends = content.stream() - .map(FriendSummaryDto::toResponse) - .toList(); - - return new FriendRequestsSliceResponse(friends, hasNext); - } -} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java index 8e8c32667..545cf6434 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java @@ -18,7 +18,13 @@ Slice findFriendsSlice( final ZonedDateTime createdAt ); - Slice findFriendRequestsSlice( + Slice findFriendReceptionInvitesSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ); + + Slice findFriendSendingInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 7bc29fdd2..270870bc4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -56,7 +56,7 @@ public Slice findFriendsSlice( .from(memberFriend) .innerJoin(member).on(memberFriend.owner.id.eq(member.id)) .innerJoin(member).on(memberFriend.friend.id.eq(member.id)) - .where(memberFriend.owner.id.eq(memberId).and(memberFriend.createdAt.lt(createdAt))) + .where(memberFriend.owner.id.eq(memberId).and(memberFriend.createdAt.loe(createdAt))) .limit(size + 1) .fetch(); @@ -89,7 +89,7 @@ public Slice findFriendsBeforeGroupInvite( .innerJoin(member).on(memberFriend.owner.id.eq(member.id)) .innerJoin(member).on(memberFriend.friend.id.eq(member.id)) .where(memberFriend.owner.id.eq(request.memberId()) - .and(memberFriend.createdAt.lt(request.createdAt())) + .and(memberFriend.createdAt.loe(request.createdAt())) .and(memberFriend.friend.id.notIn( select(memberGroup.member.id) .from(memberGroup) @@ -102,7 +102,7 @@ public Slice findFriendsBeforeGroupInvite( return getFriendSummaryDtos(request.size(), friends); } - public Slice findFriendRequestsSlice( + public Slice findFriendReceptionInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt @@ -119,7 +119,32 @@ public Slice findFriendRequestsSlice( ) .from(friendInvite) .join(friendInvite.owner, member) - .where(friendInvite.friend.id.eq(memberId).and(friendInvite.createdAt.lt(createdAt))) + .where(friendInvite.friend.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) + .limit(size + 1) + .fetch(); + + return getFriendSummaryDtos(size, friends); + } + + @Override + public Slice findFriendSendingInvitesSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + final List friends = jpaQueryFactory + .select( + Projections.constructor( + FriendSummaryDto.class, + friendInvite.owner.id, + friendInvite.owner.profileUrl, + friendInvite.owner.nickname, + friendInvite.createdAt + ) + ) + .from(friendInvite) + .join(friendInvite.owner, member) + .where(friendInvite.owner.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) .limit(size + 1) .fetch(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index d34230f0a..6a179fdc1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -34,12 +34,20 @@ public Slice findFriendsBeforeGroupInviteSlice( return memberFriendRepository.findFriendsBeforeGroupInvite(request); } - public Slice findFriendRequestsSlice( + public Slice findFriendReceptionInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt ) { - return memberFriendRepository.findFriendRequestsSlice(memberId, size, createdAt); + return memberFriendRepository.findFriendReceptionInvitesSlice(memberId, size, createdAt); + } + + public Slice findFriendSendingInvitesSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + return memberFriendRepository.findFriendSendingInvitesSlice(memberId, size, createdAt); } public List findFriendsByPhone( From b3a1a1ee85dfddc027d1eb63431e898bdd9fd53e Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 16:06:56 +0900 Subject: [PATCH 083/195] =?UTF-8?q?test=20:=20=EA=B8=B0=EC=A1=B4=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B9=9C=EA=B5=AC=20=EB=B3=B4=EB=82=B8=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=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 --- .../MemberFriendQueryRepositoryTest.java | 178 +++++++++++++++--- 1 file changed, 154 insertions(+), 24 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index 09ae4cf3d..339194a74 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -41,10 +41,11 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private static final String PROPAGATION_REQUIRES_NEW = "PROPAGATION_REQUIRES_NEW"; - private static final int MAX_COUNT = 10; + private static final int MAX_COUNT = 40; private static final Long FRIEND_START_ID = 2L; - private static final Long FRIEND_ID_TO_INVITE_OWNER = 12L; - private static final Long NOT_FRIEND_MEMBER_START_ID = 13L; + private static final Long NOT_FRIEND_MEMBER_START_ID = FRIEND_START_ID + MAX_COUNT; + private static final Long FRIEND_RECEPTION_INVITE_MEMBER_START_ID = NOT_FRIEND_MEMBER_START_ID + MAX_COUNT; + private static final Long FRIEND_SENDING_INVITE_MEMBER_START_ID = FRIEND_RECEPTION_INVITE_MEMBER_START_ID + MAX_COUNT; private final MemberFriendQueryRepository memberFriendQueryRepository; @@ -91,15 +92,6 @@ void setup(@Autowired EntityManager entityManager, friendId = friends.get(0).getId(); friendTag = friends.get(0).getTag(); - // owner에게 친구 요청만 보낸 멤버 데이터 - Member inviteFriendToOwner = MemberFixture.member(FRIEND_ID_TO_INVITE_OWNER.intValue()); - entityManager.persist(inviteFriendToOwner); - friendInviteTag =inviteFriendToOwner.getTag(); - - FriendInvite friendInvite = FriendInviteFixture.friendInvite(inviteFriendToOwner, - owner); - entityManager.persist(friendInvite); - //owner와 친구가 아닌 멤버 데이터 List notFriendMembers = MemberFixture.members( NOT_FRIEND_MEMBER_START_ID.intValue(), @@ -108,11 +100,31 @@ void setup(@Autowired EntityManager entityManager, entityManager.persist(notFriend); hashedNotFriendPhones.add(notFriend.getPhone_hash()); } - //owner에게 친구 요청을 보내지 않은 데이터 notFriendInviteTag = notFriendMembers.get(0).getTag(); //owner와 친구가 아닌 데이터 notFriendTag = notFriendMembers.get(0).getTag(); + + // owner에게 친구 요청만 받은 멤버 데이터 + List friendReceptionInviteMembers = MemberFixture.members( + FRIEND_RECEPTION_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); + for (Member member : friendReceptionInviteMembers) { + entityManager.persist(member); + + FriendInvite receptionInvite = FriendInviteFixture.friendInvite(owner, member); + entityManager.persist(receptionInvite); + } + friendInviteTag = friendReceptionInviteMembers.get(0).getTag(); + + // owner에게 친구 요청만 보낸 멤버 데이터 + List friendSendingInviteMembers = MemberFixture.members( + FRIEND_SENDING_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); + for (Member member : friendSendingInviteMembers) { + entityManager.persist(member); + + FriendInvite sendingInvite = FriendInviteFixture.friendInvite(member, owner); + entityManager.persist(sendingInvite); + } }); } @@ -125,8 +137,8 @@ void clear(@Autowired EntityManager entityManager) { entityManager.createNativeQuery("SET FOREIGN_KEY_CHECKS=1").executeUpdate(); } - @ParameterizedTest @ValueSource(ints = {2, 7, 10, 5}) + @ParameterizedTest void 사용자가_친구_목록_조회하면_친구_관계를_맺은_사용자_리스트가_나온다(int size) { //given ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); @@ -149,6 +161,28 @@ void clear(@Autowired EntityManager entityManager) { ); } + @Test + void 사용자가_첫_페이지_이후의_친구_목록_조회하면_다음_페이지의_친구_관계를_맺은_사용자_리스트가_나온다() { + //given + int size = 20; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + Slice firstSlice = memberFriendQueryRepository.findFriendsSlice( + ownerId, + size, + now + ); + + //when + FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); + Slice nextSlice = memberFriendQueryRepository.findFriendsSlice( + ownerId, + size, + dto.createdAt() + ); + + assertThat(nextSlice.getNumberOfElements()).isPositive(); + } + @Test void 친구가_친구_목록_조회하면_친구_관계를_맺은_사용자_리스트가_나온다() { //given @@ -166,14 +200,14 @@ void clear(@Autowired EntityManager entityManager) { } @Test - void 친구_요청만_보낸_사용자가_친구_목록_조회하면_리스트가_나온다() { + void 친구_요청만_보낸_사용자가_친구_목록_조회하면_빈_리스트가_나온다() { //given - int size = 20; + int size = 10; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); //when Slice slice = memberFriendQueryRepository.findFriendsSlice( - FRIEND_ID_TO_INVITE_OWNER, + FRIEND_RECEPTION_INVITE_MEMBER_START_ID, size, now ); @@ -185,7 +219,7 @@ void clear(@Autowired EntityManager entityManager) { @Test void 사용자가_유효하지_않은_시간으로_사용자의_친구_목록_조회하면_빈_리스트가_나온다() { //given - int size = 20; + int size = 10; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); //when @@ -202,7 +236,7 @@ void clear(@Autowired EntityManager entityManager) { @Test void 친구가_없는_사용자가_친구_목록_조회하면_빈_리스트가_나온다() { //given - int size = 20; + int size = 10; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); //when @@ -216,14 +250,45 @@ void clear(@Autowired EntityManager entityManager) { assertThat(slice).isEmpty(); } + @ValueSource(ints = {2, 7, 10, 5}) + @ParameterizedTest + void 사용자가_보낸_친구_요청_받은_목록을_조회하면_보낸_친구_요청목록이_나온다(int size) { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + + Slice slice = memberFriendQueryRepository.findFriendSendingInvitesSlice( + ownerId, + size, + now + ); + + assertThat(slice.getNumberOfElements()).isEqualTo(size); + } + @Test - void 사용자가_유효하지_않은_시간으로_사용자의_친구_요청_목록_조회하면_빈_리스트가_나온다() { + void 사용자가_첫_페이지_이후의_친구요청_보낸_목록을_조회하면_다음_페이지의_보낸_친구_요청목록이_나온다() { //given int size = 20; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + Slice firstSlice = memberFriendQueryRepository.findFriendSendingInvitesSlice( + ownerId, size, now); + + //when + FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); + Slice nextSlice = memberFriendQueryRepository.findFriendSendingInvitesSlice( + ownerId, size, dto.createdAt()); + + //then + assertThat(nextSlice.getNumberOfElements()).isPositive(); + } + + @Test + void 사용자가_유효하지_않은_시간으로_사용자의_친구_요청_보낸_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); //when - Slice slice = memberFriendQueryRepository.findFriendRequestsSlice( + Slice slice = memberFriendQueryRepository.findFriendSendingInvitesSlice( ownerId, size, now @@ -234,13 +299,78 @@ void clear(@Autowired EntityManager entityManager) { } @Test - void 친구가_없는_사용자가_친구_요청_목록_조회하면_빈_리스트가_나온다() { + void 친구_요청을_보내지_않은_사용자가_친구_요청_보낸_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); + + //when + Slice slice = memberFriendQueryRepository.findFriendSendingInvitesSlice( + NOT_FRIEND_MEMBER_START_ID, + size, + now + ); + + //then + assertThat(slice).isEmpty(); + } + + @ValueSource(ints = {2, 7, 10, 5}) + @ParameterizedTest + void 사용자가_받은_친구_요청_받은_목록을_조회하면_받은_친구_요청목록이_나온다(int size) { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + + Slice slice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( + ownerId, + size, + now + ); + + assertThat(slice.getNumberOfElements()).isEqualTo(size); + } + + @Test + void 사용자가_첫_페이지_이후의_친구요청_받은_목록을_조회하면_다음_페이지의_받은_친구_요청목록이_나온다() { //given int size = 20; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + Slice firstSlice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( + ownerId, size, now); + + //when + FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); + Slice nextSlice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( + ownerId, size, dto.createdAt()); + + //then + assertThat(nextSlice.getNumberOfElements()).isPositive(); + } + + @Test + void 사용자가_유효하지_않은_시간으로_사용자의_친구_요청_받은_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); + + //when + Slice slice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( + ownerId, + size, + now + ); + + //then + assertThat(slice).isEmpty(); + } + + @Test + void 친구_요청을_받지_않은_사용자가_친구_요청_받은_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); //when - Slice slice = memberFriendQueryRepository.findFriendRequestsSlice( + Slice slice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( NOT_FRIEND_MEMBER_START_ID, size, now @@ -371,7 +501,7 @@ void clear(@Autowired EntityManager entityManager) { ownerId, friendInviteTag).orElseThrow(); //then - assertThat(dto.isFriendInviteToMe()).isTrue(); + assertThat(dto.isFriendInviteToMe() || dto.isFriendInviteToFriend()).isTrue(); } @Test From 041faddfd3005a66e8b4c22b6e5a66f9347b3529 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 16:22:12 +0900 Subject: [PATCH 084/195] =?UTF-8?q?fix=20:=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MemberFriendQueryRepositoryImpl.java | 6 ++-- .../MemberFriendQueryRepositoryTest.java | 28 +++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 270870bc4..2b436f7cd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -136,9 +136,9 @@ public Slice findFriendSendingInvitesSlice( .select( Projections.constructor( FriendSummaryDto.class, - friendInvite.owner.id, - friendInvite.owner.profileUrl, - friendInvite.owner.nickname, + friendInvite.friend.id, + friendInvite.friend.profileUrl, + friendInvite.friend.nickname, friendInvite.createdAt ) ) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index 339194a74..4c6cf98c0 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -44,8 +44,10 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private static final int MAX_COUNT = 40; private static final Long FRIEND_START_ID = 2L; private static final Long NOT_FRIEND_MEMBER_START_ID = FRIEND_START_ID + MAX_COUNT; - private static final Long FRIEND_RECEPTION_INVITE_MEMBER_START_ID = NOT_FRIEND_MEMBER_START_ID + MAX_COUNT; - private static final Long FRIEND_SENDING_INVITE_MEMBER_START_ID = FRIEND_RECEPTION_INVITE_MEMBER_START_ID + MAX_COUNT; + private static final Long FRIEND_RECEPTION_INVITE_MEMBER_START_ID = + NOT_FRIEND_MEMBER_START_ID + MAX_COUNT; + private static final Long FRIEND_SENDING_INVITE_MEMBER_START_ID = + FRIEND_RECEPTION_INVITE_MEMBER_START_ID + MAX_COUNT; private final MemberFriendQueryRepository memberFriendQueryRepository; @@ -54,6 +56,8 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private final List hashedNotFriendPhones = new ArrayList<>(); private Long ownerId; private Long friendId; + private Long ownerInviteSendingStartId; + private Long ownerInviteReceptionStartId; private String friendTag; private String notFriendTag; private String friendInviteTag; @@ -106,25 +110,27 @@ void setup(@Autowired EntityManager entityManager, notFriendTag = notFriendMembers.get(0).getTag(); // owner에게 친구 요청만 받은 멤버 데이터 - List friendReceptionInviteMembers = MemberFixture.members( + List receptionInviteToOwnerMembers = MemberFixture.members( FRIEND_RECEPTION_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); - for (Member member : friendReceptionInviteMembers) { + for (Member member : receptionInviteToOwnerMembers) { entityManager.persist(member); FriendInvite receptionInvite = FriendInviteFixture.friendInvite(owner, member); entityManager.persist(receptionInvite); } - friendInviteTag = friendReceptionInviteMembers.get(0).getTag(); + friendInviteTag = receptionInviteToOwnerMembers.get(0).getTag(); + ownerInviteReceptionStartId = receptionInviteToOwnerMembers.get(0).getId(); // owner에게 친구 요청만 보낸 멤버 데이터 - List friendSendingInviteMembers = MemberFixture.members( + List sendingInviteToOwnerMembers = MemberFixture.members( FRIEND_SENDING_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); - for (Member member : friendSendingInviteMembers) { + for (Member member : sendingInviteToOwnerMembers) { entityManager.persist(member); FriendInvite sendingInvite = FriendInviteFixture.friendInvite(member, owner); entityManager.persist(sendingInvite); } + ownerInviteSendingStartId = sendingInviteToOwnerMembers.get(0).getId(); }); } @@ -261,7 +267,9 @@ void clear(@Autowired EntityManager entityManager) { now ); - assertThat(slice.getNumberOfElements()).isEqualTo(size); + assertThat(slice.getContent()).isNotEmpty(); + assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteReceptionStartId + && dto.id() < ownerInviteReceptionStartId + MAX_COUNT); } @Test @@ -326,7 +334,9 @@ void clear(@Autowired EntityManager entityManager) { now ); - assertThat(slice.getNumberOfElements()).isEqualTo(size); + assertThat(slice.getContent()).isNotEmpty(); + assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteSendingStartId + && dto.id() < ownerInviteSendingStartId + MAX_COUNT); } @Test From 194d98e21d75ef3e8f9a19d211a9cf70996d2891 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 17:12:22 +0900 Subject: [PATCH 085/195] =?UTF-8?q?feat=20:=20=EA=B8=B0=EC=A1=B4=20API=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EC=B4=88=EB=8C=80=20=EB=B3=B4=EB=82=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 API 메서드명 통일성을 위해 변경 - 기존 API 쿼리에 where 절에 조건 없어서 추가 - 그룹 초대 보낸 목록 조회 API 추가 --- .gitignore | 2 +- .../api/query/MemberGroupQueryApi.java | 33 +++++++++++-- .../query/MemberGroupQueryApiController.java | 44 +++++++++++++++--- .../data/dto/GroupInviteSummaryDto.java | 6 +-- .../data/dto/GroupSendingInviteMemberDto.java | 22 +++++++++ .../dto/GroupSendingInvitesRequestDto.java | 20 ++++++++ ... GroupReceptionInviteSummaryResponse.java} | 4 +- ...> GroupReceptionInvitesSliceResponse.java} | 11 +++-- .../GroupSendingInviteMemberResponse.java | 19 ++++++++ .../GroupSendingInvitesSliceResponse.java | 26 +++++++++++ .../GroupInviteQueryRepository.java | 6 ++- .../GroupInviteQueryRepositoryImpl.java | 46 ++++++++++++++++--- .../service/MemberGroupQueryService.java | 11 ++++- .../GroupInviteQueryRepositoryTest.java | 6 +-- 14 files changed, 221 insertions(+), 35 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesRequestDto.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/{GroupInviteSummaryResponse.java => GroupReceptionInviteSummaryResponse.java} (89%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/{GroupInviteSummaryResponses.java => GroupReceptionInvitesSliceResponse.java} (62%) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java diff --git a/.gitignore b/.gitignore index 91cd78597..51e94bb56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea -data \ No newline at end of file +backend/data \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java index 18e84fcf6..38edc4c66 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java @@ -8,15 +8,16 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import java.time.ZonedDateTime; import org.springframework.http.ResponseEntity; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponses; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInvitesSliceResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesSliceResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; public interface MemberGroupQueryApi { @Operation( - summary = "그룹 요청 목록 조회", - description = "사용자애게 그룹 초대 요청이 온 그룹 목록을 조회한다.", + summary = "그룹 요청 받은 목록 조회", + description = "사용자에게 그룹 초대 요청이 온 모든 그룹 목록을 조회한다.", security = {@SecurityRequirement(name = "user_token")}, tags = {"member group"} ) @@ -26,7 +27,7 @@ public interface MemberGroupQueryApi { description = "ok" ) }) - ResponseEntity> findGroupInvites( + ResponseEntity> findGroupReceptionInvites( Long memberId, @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) @@ -36,4 +37,28 @@ ResponseEntity> findGroupInvites( ZonedDateTime createdAt ); + @Operation( + summary = "그룹 요청 보낸 목록 조회", + description = "그룹장이 그룹 별로 그룹 초대 요청을 보낸 사용자 목록을 조회한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"member group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findGroupSendingInvites( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) + Long groupId, + + @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/member_group/api/query/MemberGroupQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java index bbdbe2a36..72f18e470 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java @@ -6,11 +6,15 @@ 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.member_group.data.dto.GroupInviteSummaryDto; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponses; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInvitesSliceResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesSliceResponse; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupQueryService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -26,28 +30,54 @@ public class MemberGroupQueryApiController implements MemberGroupQueryApi { @GetMapping( - value = "/invites", + value = "/reception-invites", produces = {"application/json"} ) @Override - public ResponseEntity> findGroupInvites( + public ResponseEntity> findGroupReceptionInvites( @AuthenticationPrincipal final Long memberId, @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice groupInviteSummaryDtos = memberGroupQueryService.findGroupInvites( + final Slice groupInviteSummarySlice = memberGroupQueryService.findGroupReceptionInvitesSlice( memberId, size, createdAt); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - GroupInviteSummaryResponses.createOf( - groupInviteSummaryDtos.getContent(), - groupInviteSummaryDtos.hasNext(), + GroupReceptionInvitesSliceResponse.createOf( + groupInviteSummarySlice.getContent(), + groupInviteSummarySlice.hasNext(), s3PreSignedUrlManager::getS3PreSignedUrlForGet ) ) ); } + @GetMapping( + value = "/{group_id}/sending-invites", + produces = {"application/json"} + ) + @Override + public ResponseEntity> findGroupSendingInvites( + @AuthenticationPrincipal final Long memberId, + @PathVariable(value = "group_id") final Long groupId, + @RequestParam(defaultValue = "20", value = "size") final int size, + @RequestParam(value = "created_at") final ZonedDateTime createdAt + ) { + GroupSendingInvitesRequestDto dto = GroupSendingInvitesRequestDto.create( + memberId, groupId, size, createdAt); + Slice groupSendingInvitesSlice = memberGroupQueryService.findGroupSendingInvites( + dto); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + GroupSendingInvitesSliceResponse.createOf( + groupSendingInvitesSlice.getContent(), + groupSendingInvitesSlice.hasNext() + ) + ) + ); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java index 140f58ec7..8c4c7fe55 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java @@ -3,7 +3,7 @@ import java.time.ZonedDateTime; import java.util.function.Function; import lombok.Builder; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInviteSummaryResponse; @Builder public record GroupInviteSummaryDto( @@ -16,10 +16,10 @@ public record GroupInviteSummaryDto( String groupOwnerName ) { - public GroupInviteSummaryResponse toResponse( + public GroupReceptionInviteSummaryResponse toResponse( final Function preSignedUrlFunction ) { - return GroupInviteSummaryResponse.builder() + return GroupReceptionInviteSummaryResponse.builder() .groupId(groupId) .groupName(groupName) .groupProfileUrl(preSignedUrlFunction.apply(groupProfileUrl)) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java new file mode 100644 index 000000000..2fcb0f82b --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java @@ -0,0 +1,22 @@ +package site.timecapsulearchive.core.domain.member_group.data.dto; + + +import java.time.ZonedDateTime; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInviteMemberResponse; + +public record GroupSendingInviteMemberDto( + Long id, + String nickname, + String profileUrl, + ZonedDateTime sendingInvitesCreatedAt +) { + + public GroupSendingInviteMemberResponse toResponse() { + return GroupSendingInviteMemberResponse.builder() + .id(id) + .nickname(nickname) + .profileUrl(profileUrl) + .sendingInvitesCreatedAt(sendingInvitesCreatedAt) + .build(); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesRequestDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesRequestDto.java new file mode 100644 index 000000000..73138d8d5 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesRequestDto.java @@ -0,0 +1,20 @@ +package site.timecapsulearchive.core.domain.member_group.data.dto; + +import java.time.ZonedDateTime; + +public record GroupSendingInvitesRequestDto( + Long memberId, + Long groupId, + int size, + ZonedDateTime createdAt +) { + + public static GroupSendingInvitesRequestDto create( + final Long memberId, + final Long groupId, + final int size, + final ZonedDateTime createdAt + ) { + return new GroupSendingInvitesRequestDto(memberId, groupId, size, createdAt); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java similarity index 89% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponse.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java index 77acf1acf..dffd6d1b5 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java @@ -7,7 +7,7 @@ @Builder @Schema(description = "초대온 그룹 요약 정보") -public record GroupInviteSummaryResponse( +public record GroupReceptionInviteSummaryResponse( @Schema(description = "그룹 아이디") Long groupId, @@ -28,7 +28,7 @@ public record GroupInviteSummaryResponse( String groupOwnerName ) { - public GroupInviteSummaryResponse { + public GroupReceptionInviteSummaryResponse { if (createdAt != null) { createdAt = createdAt.withZoneSameInstant(ResponseMappingConstant.ZONE_ID); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponses.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInvitesSliceResponse.java similarity index 62% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponses.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInvitesSliceResponse.java index 4dd508058..95e5acdd8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupInviteSummaryResponses.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInvitesSliceResponse.java @@ -5,22 +5,23 @@ import java.util.function.Function; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; -public record GroupInviteSummaryResponses( +public record GroupReceptionInvitesSliceResponse( @Schema(description = "초대 온 그룹 요약 정보 리스트") - List responses, + List responses, @Schema(description = "다음 페이지 유무") Boolean hasNext ) { - public static GroupInviteSummaryResponses createOf( + public static GroupReceptionInvitesSliceResponse createOf( final List dtos, final boolean hasNext, final Function preSignedUrlFunction ) { - List groupInviteSummaryResponses = dtos.stream() + List groupReceptionInviteSummaryResponses = dtos.stream() .map(dto -> dto.toResponse(preSignedUrlFunction)).toList(); - return new GroupInviteSummaryResponses(groupInviteSummaryResponses, hasNext); + return new GroupReceptionInvitesSliceResponse(groupReceptionInviteSummaryResponses, + hasNext); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java new file mode 100644 index 000000000..b4193f30f --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java @@ -0,0 +1,19 @@ +package site.timecapsulearchive.core.domain.member_group.data.response; + +import java.time.ZonedDateTime; +import lombok.Builder; +import site.timecapsulearchive.core.global.common.response.ResponseMappingConstant; + +@Builder +public record GroupSendingInviteMemberResponse( + Long id, + String nickname, + String profileUrl, + ZonedDateTime sendingInvitesCreatedAt +) { + public GroupSendingInviteMemberResponse { + if (sendingInvitesCreatedAt != null) { + sendingInvitesCreatedAt = sendingInvitesCreatedAt.withZoneSameInstant(ResponseMappingConstant.ZONE_ID); + } + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java new file mode 100644 index 000000000..c584e87a3 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java @@ -0,0 +1,26 @@ +package site.timecapsulearchive.core.domain.member_group.data.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; + +@Schema(description = "그룹 초대 보낸 목록") +public record GroupSendingInvitesSliceResponse( + @Schema(description = "초대 보낸 그룹원 정보 리스트") + List responses, + + @Schema(description = "다음 페이지 유무") + Boolean hasNext +) { + + public static GroupSendingInvitesSliceResponse createOf( + final List groupSendingInviteMemberDtos, + final boolean hasNext + ) { + List groupSendingInviteMemberResponses = groupSendingInviteMemberDtos.stream() + .map(GroupSendingInviteMemberDto::toResponse) + .toList(); + + return new GroupSendingInvitesSliceResponse(groupSendingInviteMemberResponses, hasNext); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java index 7d1725f84..8a5ea2991 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -4,6 +4,8 @@ import java.util.List; import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; public interface GroupInviteQueryRepository { @@ -11,9 +13,11 @@ public interface GroupInviteQueryRepository { List findGroupInviteIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); - Slice findGroupInvitesSummary( + Slice findGroupRecetpionInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt ); + + Slice findGroupSendingInvitesSlice(GroupSendingInvitesRequestDto dto); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 2936216ef..ba90e69cb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -21,6 +21,8 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; @Repository @RequiredArgsConstructor @@ -71,7 +73,7 @@ public List findGroupInviteIdsByGroupIdAndGroupOwnerId( } @Override - public Slice findGroupInvitesSummary( + public Slice findGroupRecetpionInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt @@ -87,21 +89,51 @@ public Slice findGroupInvitesSummary( group.createdAt, member.nickname ) - ) .from(groupInvite) .join(groupInvite.group, group) - .join(groupInvite.groupOwner, member).on(groupInvite.groupMember.id.eq(memberId)) - .where(groupInvite.createdAt.lt(createdAt)) + .join(groupInvite.groupOwner, member) + .where(groupInvite.groupMember.id.eq(memberId).and(groupInvite.createdAt.lt(createdAt))) .limit(size + 1) .fetch(); - final boolean hasNext = groupInviteSummaryDtos.size() > size; + return makeSlice(size, groupInviteSummaryDtos); + } + + private Slice makeSlice( + final int size, + final List dtos + ) { + final boolean hasNext = dtos.size() > size; if (hasNext) { - groupInviteSummaryDtos.remove(size); + dtos.remove(size); } - return new SliceImpl<>(groupInviteSummaryDtos, Pageable.ofSize(size), hasNext); + return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); } + @Override + public Slice findGroupSendingInvitesSlice( + final GroupSendingInvitesRequestDto dto + ) { + final List groupSendingInviteMemberDtos = jpaQueryFactory + .select( + Projections.constructor( + GroupSendingInviteMemberDto.class, + member.id, + member.nickname, + member.profileUrl, + groupInvite.createdAt + ) + ) + .from(groupInvite) + .join(groupInvite.groupMember, member) + .where(groupInvite.group.id.eq(dto.groupId()) + .and(groupInvite.groupOwner.id.eq(dto.memberId())) + .and(groupInvite.createdAt.loe(dto.createdAt()))) + .limit(dto.size() + 1) + .fetch(); + + return makeSlice(dto.size(), groupSendingInviteMemberDtos); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java index 0bf45099e..a6734bbea 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -6,6 +6,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; @Service @@ -15,12 +17,17 @@ public class MemberGroupQueryService { private final GroupInviteRepository groupInviteRepository; - public Slice findGroupInvites( + public Slice findGroupReceptionInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt ) { - return groupInviteRepository.findGroupInvitesSummary(memberId, size, createdAt); + return groupInviteRepository.findGroupRecetpionInvitesSlice(memberId, size, createdAt); } + public Slice findGroupSendingInvites( + final GroupSendingInvitesRequestDto dto + ) { + return groupInviteRepository.findGroupSendingInvitesSlice(dto); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java index 52048ddda..d2fec9d12 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -72,7 +72,7 @@ void setUp(@Autowired EntityManager entityManager) { ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); //when - Slice groupInvitesSummary = groupInviteRepository.findGroupInvitesSummary( + Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( memberId, size, createAt); //then @@ -87,7 +87,7 @@ void setUp(@Autowired EntityManager entityManager) { ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); //when - Slice groupInvitesSummary = groupInviteRepository.findGroupInvitesSummary( + Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( memberId, size, createAt); //then @@ -108,7 +108,7 @@ void setUp(@Autowired EntityManager entityManager) { ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); //when - Slice groupInvitesSummary = groupInviteRepository.findGroupInvitesSummary( + Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( memberId, size, createAt); //then From aaad884b391a16d3cfcb764f10995941a897eb88 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 17:25:37 +0900 Subject: [PATCH 086/195] =?UTF-8?q?fix=20:=20=EC=B4=88=EB=8C=80=ED=95=9C?= =?UTF-8?q?=20=EA=B7=B8=EB=A3=B9=EC=9B=90=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8A=A4=ED=8E=99=20=EB=B3=80=EA=B2=BD,=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 현재 그룹원 포함 최대 30명까지 초대 가능하므로 슬라이스 대신 리스트로 변경 - 패키지 이름 변경 --- .../service/command/GroupCommandService.java | 4 ++-- .../api/query/MemberGroupQueryApi.java | 14 ++++--------- .../query/MemberGroupQueryApiController.java | 21 +++++++------------ ....java => GroupSendingInvitesResponse.java} | 15 ++++++------- .../GroupInviteQueryRepository.java | 8 ++++--- .../GroupInviteQueryRepositoryImpl.java | 18 +++++++--------- .../GroupInviteRepository.java | 2 +- .../MemberGroupQueryRepository.java | 2 +- .../MemberGroupQueryRepositoryImpl.java | 2 +- .../MemberGroupRepository.java | 2 +- .../service/MemberGroupCommandService.java | 4 ++-- .../service/MemberGroupQueryService.java | 11 +++++----- .../service/GroupCommandServiceTest.java | 4 ++-- .../MemberGroupQueryRepositoryTest.java | 8 +++---- .../MemberGroupCommandServiceTest.java | 4 ++-- 15 files changed, 51 insertions(+), 68 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/{GroupSendingInvitesSliceResponse.java => GroupSendingInvitesResponse.java} (63%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/{groupInviteRepository => group_invite_repository}/GroupInviteQueryRepository.java (77%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/{groupInviteRepository => group_invite_repository}/GroupInviteQueryRepositoryImpl.java (87%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/{groupInviteRepository => group_invite_repository}/GroupInviteRepository.java (96%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/{memberGroupRepository => member_group_repository}/MemberGroupQueryRepository.java (93%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/{memberGroupRepository => member_group_repository}/MemberGroupQueryRepositoryImpl.java (98%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/{memberGroupRepository => member_group_repository}/MemberGroupRepository.java (95%) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java index c15b73e01..0b7d4d9ce 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/command/GroupCommandService.java @@ -20,8 +20,8 @@ import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; import site.timecapsulearchive.core.infra.s3.manager.S3ObjectManager; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java index 38edc4c66..b3fc268bd 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java @@ -9,7 +9,7 @@ import java.time.ZonedDateTime; import org.springframework.http.ResponseEntity; import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInvitesSliceResponse; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesSliceResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; public interface MemberGroupQueryApi { @@ -39,7 +39,7 @@ ResponseEntity> findGroupReceptionIn @Operation( summary = "그룹 요청 보낸 목록 조회", - description = "그룹장이 그룹 별로 그룹 초대 요청을 보낸 사용자 목록을 조회한다.", + description = "그룹장이 그룹 별로 그룹 초대 요청을 보낸 사용자 목록을 조회한다. 최대 30개가 반환된다.", security = {@SecurityRequirement(name = "user_token")}, tags = {"member group"} ) @@ -49,16 +49,10 @@ ResponseEntity> findGroupReceptionIn description = "ok" ) }) - ResponseEntity> findGroupSendingInvites( + ResponseEntity> findGroupSendingInvites( Long memberId, @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) - Long groupId, - - @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) - int size, - - @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) - ZonedDateTime createdAt + Long groupId ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java index 72f18e470..0685b9703 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.member_group.api.query; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.http.ResponseEntity; @@ -12,9 +13,8 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; -import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInvitesSliceResponse; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesSliceResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesResponse; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupQueryService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -59,24 +59,17 @@ public ResponseEntity> findGroupRece produces = {"application/json"} ) @Override - public ResponseEntity> findGroupSendingInvites( + public ResponseEntity> findGroupSendingInvites( @AuthenticationPrincipal final Long memberId, - @PathVariable(value = "group_id") final Long groupId, - @RequestParam(defaultValue = "20", value = "size") final int size, - @RequestParam(value = "created_at") final ZonedDateTime createdAt + @PathVariable(value = "group_id") final Long groupId ) { - GroupSendingInvitesRequestDto dto = GroupSendingInvitesRequestDto.create( - memberId, groupId, size, createdAt); - Slice groupSendingInvitesSlice = memberGroupQueryService.findGroupSendingInvites( - dto); + List groupSendingInvites = memberGroupQueryService.findGroupSendingInvites( + memberId, groupId); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - GroupSendingInvitesSliceResponse.createOf( - groupSendingInvitesSlice.getContent(), - groupSendingInvitesSlice.hasNext() - ) + GroupSendingInvitesResponse.createOf(groupSendingInvites) ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesResponse.java similarity index 63% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesResponse.java index c584e87a3..4be590970 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesResponse.java @@ -5,22 +5,19 @@ import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; @Schema(description = "그룹 초대 보낸 목록") -public record GroupSendingInvitesSliceResponse( - @Schema(description = "초대 보낸 그룹원 정보 리스트") - List responses, +public record GroupSendingInvitesResponse( - @Schema(description = "다음 페이지 유무") - Boolean hasNext + @Schema(description = "초대 보낸 그룹원 정보 리스트") + List responses ) { - public static GroupSendingInvitesSliceResponse createOf( - final List groupSendingInviteMemberDtos, - final boolean hasNext + public static GroupSendingInvitesResponse createOf( + final List groupSendingInviteMemberDtos ) { List groupSendingInviteMemberResponses = groupSendingInviteMemberDtos.stream() .map(GroupSendingInviteMemberDto::toResponse) .toList(); - return new GroupSendingInvitesSliceResponse(groupSendingInviteMemberResponses, hasNext); + return new GroupSendingInvitesResponse(groupSendingInviteMemberResponses); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java similarity index 77% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java index 8a5ea2991..899883909 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java @@ -1,11 +1,10 @@ -package site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository; import java.time.ZonedDateTime; import java.util.List; import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; -import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; public interface GroupInviteQueryRepository { @@ -19,5 +18,8 @@ Slice findGroupRecetpionInvitesSlice( final ZonedDateTime createdAt ); - Slice findGroupSendingInvitesSlice(GroupSendingInvitesRequestDto dto); + List findGroupSendingInvites( + final Long memberId, + final Long groupId + ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java similarity index 87% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index ba90e69cb..c76739b55 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; @@ -22,7 +22,6 @@ import org.springframework.stereotype.Repository; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; -import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; @Repository @RequiredArgsConstructor @@ -113,10 +112,11 @@ private Slice makeSlice( } @Override - public Slice findGroupSendingInvitesSlice( - final GroupSendingInvitesRequestDto dto + public List findGroupSendingInvites( + final Long memberId, + final Long groupId ) { - final List groupSendingInviteMemberDtos = jpaQueryFactory + return jpaQueryFactory .select( Projections.constructor( GroupSendingInviteMemberDto.class, @@ -128,12 +128,8 @@ public Slice findGroupSendingInvitesSlice( ) .from(groupInvite) .join(groupInvite.groupMember, member) - .where(groupInvite.group.id.eq(dto.groupId()) - .and(groupInvite.groupOwner.id.eq(dto.memberId())) - .and(groupInvite.createdAt.loe(dto.createdAt()))) - .limit(dto.size() + 1) + .where(groupInvite.group.id.eq(groupId) + .and(groupInvite.groupOwner.id.eq(memberId))) .fetch(); - - return makeSlice(dto.size(), groupSendingInviteMemberDtos); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java similarity index 96% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java index e429c0346..51b5f37e7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository; +package site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository; import java.util.List; import org.springframework.data.jpa.repository.Modifying; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java similarity index 93% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java index 963047020..8cf25fa5a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.member_group.repository.member_group_repository; import java.util.Optional; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java similarity index 98% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java index 8aa557ac2..e0b22628d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.member_group.repository.member_group_repository; import static site.timecapsulearchive.core.domain.group.entity.QGroup.group; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupRepository.java similarity index 95% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupRepository.java index 2d42643b7..6f2a9cf28 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupRepository.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository; +package site.timecapsulearchive.core.domain.member_group.repository.member_group_repository; import java.util.List; import java.util.Optional; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 7596c951d..077ff4666 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -21,8 +21,8 @@ import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Service diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java index a6734bbea..90b888beb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -1,14 +1,14 @@ package site.timecapsulearchive.core.domain.member_group.service; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; -import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesRequestDto; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; @Service @RequiredArgsConstructor @@ -25,9 +25,10 @@ public Slice findGroupReceptionInvitesSlice( return groupInviteRepository.findGroupRecetpionInvitesSlice(memberId, size, createdAt); } - public Slice findGroupSendingInvites( - final GroupSendingInvitesRequestDto dto + public List findGroupSendingInvites( + final Long memberId, + final Long groupId ) { - return groupInviteRepository.findGroupSendingInvitesSlice(dto); + return groupInviteRepository.findGroupSendingInvites(memberId, groupId); } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java index f6b55a63f..248433597 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/group/service/GroupCommandServiceTest.java @@ -34,8 +34,8 @@ import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; import site.timecapsulearchive.core.infra.s3.manager.S3ObjectManager; diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java index c0a2329ef..a9709d0bc 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java @@ -66,10 +66,6 @@ void setup(@Autowired EntityManager entityManager) { groups.add(group); } - //그룹원들 - List members = MemberFixture.members(4, 2); - members.forEach(entityManager::persist); - //그룹에 사용자를 그룹장으로 설정 for (int count = 0; count < GROUP_COUNT; count++) { MemberGroup memberGroup = MemberGroupFixture.memberGroup(member, groups.get(count), @@ -77,6 +73,10 @@ void setup(@Autowired EntityManager entityManager) { entityManager.persist(memberGroup); } + //그룹원들 + List members = MemberFixture.members(4, 2); + members.forEach(entityManager::persist); + //그룹원들 설정 List memberGroups = MemberGroupFixture.memberGroups(members, groups.get(0)); memberGroups.forEach(entityManager::persist); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 58cfe0eee..2ea2586ee 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -34,8 +34,8 @@ import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; From e0a3ee00fa56351866e7011873fe8c1ba1fb18b5 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 18:02:26 +0900 Subject: [PATCH 087/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EB=B3=B4=EB=82=B8=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20&=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member_group/data/dto/GroupInviteSummaryDto.java | 4 ++-- .../response/GroupReceptionInviteSummaryResponse.java | 9 +++++---- .../GroupInviteQueryRepositoryImpl.java | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java index 8c4c7fe55..3d3ec6570 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java @@ -12,7 +12,7 @@ public record GroupInviteSummaryDto( String groupName, String groupProfileUrl, String description, - ZonedDateTime createdAt, + ZonedDateTime groupReceptionInviteTime, String groupOwnerName ) { @@ -24,7 +24,7 @@ public GroupReceptionInviteSummaryResponse toResponse( .groupName(groupName) .groupProfileUrl(preSignedUrlFunction.apply(groupProfileUrl)) .description(description) - .createdAt(createdAt) + .groupReceptionInviteTime(groupReceptionInviteTime) .groupOwnerName(groupOwnerName) .build(); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java index dffd6d1b5..dff6de490 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java @@ -21,16 +21,17 @@ public record GroupReceptionInviteSummaryResponse( @Schema(description = "그룹 설명") String description, - @Schema(description = "그룹 생성일") - ZonedDateTime createdAt, + @Schema(description = "그룹 초대 시간") + ZonedDateTime groupReceptionInviteTime, @Schema(description = "그룹장") String groupOwnerName ) { public GroupReceptionInviteSummaryResponse { - if (createdAt != null) { - createdAt = createdAt.withZoneSameInstant(ResponseMappingConstant.ZONE_ID); + if (groupReceptionInviteTime != null) { + groupReceptionInviteTime = groupReceptionInviteTime.withZoneSameInstant( + ResponseMappingConstant.ZONE_ID); } } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index c76739b55..c7b115a58 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -85,14 +85,14 @@ public Slice findGroupRecetpionInvitesSlice( group.groupName, group.groupProfileUrl, group.groupDescription, - group.createdAt, + groupInvite.createdAt, member.nickname ) ) .from(groupInvite) .join(groupInvite.group, group) .join(groupInvite.groupOwner, member) - .where(groupInvite.groupMember.id.eq(memberId).and(groupInvite.createdAt.lt(createdAt))) + .where(groupInvite.groupMember.id.eq(memberId).and(groupInvite.createdAt.loe(createdAt))) .limit(size + 1) .fetch(); From 4f6a860695e612d716d62cae54380e664bcc23c7 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 18:04:28 +0900 Subject: [PATCH 088/195] =?UTF-8?q?test=20:=20=EA=B8=B0=EC=A1=B4=20API,=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=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 --- .../GroupInviteQueryRepositoryTest.java | 78 ++++++++++++++----- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java index d2fec9d12..1aad3c7de 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -1,12 +1,12 @@ package site.timecapsulearchive.core.domain.member_group.repository; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; -import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,16 +22,21 @@ import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteQueryRepository; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteQueryRepositoryImpl; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteQueryRepository; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteQueryRepositoryImpl; @TestConstructor(autowireMode = AutowireMode.ALL) class GroupInviteQueryRepositoryTest extends RepositoryTest { private static final int MAX_GROUP_COUNT = 2; + private final GroupInviteQueryRepository groupInviteRepository; - private Member groupMember; + + private Long groupId; + private Long groupOwnerId; + private Long groupMemberId; GroupInviteQueryRepositoryTest( JdbcTemplate jdbcTemplate, @@ -47,14 +52,17 @@ void setUp(@Autowired EntityManager entityManager) { // 그룹 초대 할 그룹장들 List groupOwners = MemberFixture.members(0, MAX_GROUP_COUNT); groupOwners.forEach(entityManager::persist); + groupOwnerId = groupOwners.get(0).getId(); //그룹 초대 올 그룹원 - groupMember = MemberFixture.member(2); + Member groupMember = MemberFixture.member(2); entityManager.persist(groupMember); + groupMemberId = groupMember.getId(); // 그룹들 List groups = GroupFixture.groups(0, MAX_GROUP_COUNT); groups.forEach(entityManager::persist); + groupId = groups.get(0).getId(); // 그룹원에게 초대온 그룹 초대들 for (int i = 0; i < MAX_GROUP_COUNT; i++) { @@ -65,9 +73,9 @@ void setUp(@Autowired EntityManager entityManager) { } @Test - void 사용자는_자신에게_온_그룹_초대_목록을_조회할_수_있다() { + void 사용자는_자신에게_온_그룹_초대_목록을_조회하면_그룹_초대목록이_나온다() { //given - Long memberId = groupMember.getId(); + Long memberId = groupMemberId; int size = 1; ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); @@ -76,13 +84,13 @@ void setUp(@Autowired EntityManager entityManager) { memberId, size, createAt); //then - assertThat(groupInvitesSummary.getContent()).isNotNull(); + assertThat(groupInvitesSummary.getContent()).isNotEmpty(); } @Test void 사용자는_자신에게_온_그룹_초대_목록에서_그룹_정보와_그룹장을_알_수_있다() { //given - Long memberId = groupMember.getId(); + Long memberId = groupMemberId; int size = 1; ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); @@ -92,28 +100,60 @@ void setUp(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { - Assertions.assertThat(groupInvitesSummary).allMatch(g -> !g.groupName().isEmpty()); - Assertions.assertThat(groupInvitesSummary) + assertThat(groupInvitesSummary).allMatch(g -> !g.groupName().isEmpty()); + assertThat(groupInvitesSummary) .allMatch(g -> !g.groupProfileUrl().isEmpty()); - Assertions.assertThat(groupInvitesSummary).allMatch(g -> !g.description().isEmpty()); - Assertions.assertThat(groupInvitesSummary).allMatch(g -> !g.groupOwnerName().isEmpty()); + assertThat(groupInvitesSummary).allMatch(g -> !g.description().isEmpty()); + assertThat(groupInvitesSummary).allMatch(g -> !g.groupOwnerName().isEmpty()); }); } @Test - void 사용자는_조회한_그룹_초대_목록_후_그룹_초대_존재_여부를_확인_할_수_있다() { + void 사용자는_그룹_초대_받은_목록_첫_페이지를_조회_후_다음_페이지에서_그룹_초대_받은_목록을_조회_할_수_있다() { //given - Long memberId = groupMember.getId(); + Long memberId = groupMemberId; int size = 1; - ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + Slice firstSlice = groupInviteRepository.findGroupRecetpionInvitesSlice( + memberId, size, now); //when + GroupInviteSummaryDto dto = firstSlice.getContent().get(0); Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( - memberId, size, createAt); + memberId, size, dto.groupReceptionInviteTime()); + + //then + assertThat(groupInvitesSummary.getContent()).isNotEmpty(); + } + + @Test + void 그룹장은_자신이_보낸_그룹_초대_목록을_조회하면_그룹_초대목록이_나온다() { + //given + //when + List groupSendingInvites = groupInviteRepository.findGroupSendingInvites( + groupOwnerId, groupId); //then - assertThat(groupInvitesSummary.hasNext()).isTrue(); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(groupSendingInvites).isNotEmpty(); + softly.assertThat(groupSendingInvites).allMatch(dto -> dto.id() != null); + softly.assertThat(groupSendingInvites) + .allMatch(dto -> dto.nickname() != null && !dto.nickname().isBlank()); + softly.assertThat(groupSendingInvites) + .allMatch(dto -> dto.profileUrl() != null && !dto.profileUrl().isBlank()); + softly.assertThat(groupSendingInvites) + .allMatch(dto -> dto.sendingInvitesCreatedAt() != null); + }); } + @Test + void 그룹원은_자신이_보낸_그룹_초대_목록을_조회하면_빈_리스트가_나온다() { + //given + //when + List groupSendingInvites = groupInviteRepository.findGroupSendingInvites( + groupMemberId, groupId); + //then + assertThat(groupSendingInvites).isEmpty(); + } } From ab92eb867e626294fd8d3a2a0412c0d77379a134 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 1 Jun 2024 18:48:29 +0900 Subject: [PATCH 089/195] =?UTF-8?q?feat=20:=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=82=AD=EC=A0=9C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../friend/api/command/FriendCommandApi.java | 26 ++++++++++++++++ .../command/FriendCommandApiController.java | 20 +++++++++++-- .../friend_invite/FriendInviteRepository.java | 8 +++++ .../service/command/FriendCommandService.java | 13 ++++++++ .../global/error/GlobalExceptionHandler.java | 30 +++++++++++++------ 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApi.java index 83f1351fe..ff56bce53 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApi.java @@ -42,6 +42,32 @@ ResponseEntity> acceptFriendRequest( Long friendId ); + @Operation( + summary = "소셜 친구 요청 삭제", + description = "사용자가 보낸 소셜 친구 요청을 삭제한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"friend"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리 완료" + ), + @ApiResponse( + responseCode = "400", + description = """ + 잘못된 요청 파라미터 또는 존재하지 않는 사용자로 요청하거나 존재하지 않는 친구에게 친구 요청을 보낼 경우 발생 + """, + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + }) + ResponseEntity> deleteSendingFriendInvite( + Long memberId, + + @Parameter(in = ParameterIn.PATH, required = true, schema = @Schema()) + Long friendId + ); + @Operation( summary = "소셜 친구 삭제", description = "사용자의 소셜 친구를 삭제한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java index cf33ef264..5ccc2e626 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; import site.timecapsulearchive.core.domain.friend.service.command.FriendCommandService; +import site.timecapsulearchive.core.domain.member.service.MemberService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -20,6 +21,7 @@ public class FriendCommandApiController implements FriendCommandApi { private final FriendCommandService friendCommandService; + private final MemberService memberService; @DeleteMapping(value = "/{friend_id}") @Override @@ -36,6 +38,22 @@ public ResponseEntity> deleteFriend( ); } + @DeleteMapping("/{friend_id}/sending-invites") + @Override + public ResponseEntity> deleteSendingFriendInvite( + @AuthenticationPrincipal final Long memberId, + @PathVariable("friend_id") final Long friendId + ) { + friendCommandService.deleteSendingFriendInvite(memberId, friendId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); + } + + @DeleteMapping("/{friend_id}/deny-request") @Override public ResponseEntity> denyFriendRequest( @@ -95,6 +113,4 @@ public ResponseEntity> acceptFriendRequest( ) ); } - - } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java index 944e7bedf..562710440 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java @@ -1,8 +1,12 @@ package site.timecapsulearchive.core.domain.friend.repository.friend_invite; +import jakarta.persistence.LockModeType; +import jakarta.persistence.QueryHint; import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; @@ -28,5 +32,9 @@ List findFriendInviteWithMembersByOwnerIdAndFriendId( int deleteFriendInviteByOwnerIdAndFriendId(Long memberId, Long targetId); void delete(FriendInvite friendInvite); + + @Lock(LockModeType.READ) + @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "3000")}) + Optional findFriendInviteForUpdateByOwnerIdAndFriendId(Long memberId, Long targetId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java index 00ada5929..8d153cce5 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java @@ -1,8 +1,10 @@ package site.timecapsulearchive.core.domain.friend.service.command; +import jakarta.persistence.PersistenceException; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +22,7 @@ import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; +@Slf4j @Service @RequiredArgsConstructor public class FriendCommandService { @@ -142,4 +145,14 @@ public void deleteFriend(final Long memberId, final Long friendId) { memberFriends.forEach(memberFriendRepository::delete); } + @Transactional + public void deleteSendingFriendInvite(final Long memberId, final Long friendId) { + validateFriendDuplicateId(memberId, friendId); + + FriendInvite friendInvite = friendInviteRepository.findFriendInviteForUpdateByOwnerIdAndFriendId( + memberId, friendId) + .orElseThrow(FriendInviteNotFoundException::new); + + friendInviteRepository.delete(friendInvite); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java index e7ef5add0..459f36da8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java @@ -6,6 +6,7 @@ import static site.timecapsulearchive.core.global.error.ErrorCode.REQUEST_PARAMETER_NOT_FOUND_ERROR; import static site.timecapsulearchive.core.global.error.ErrorCode.REQUEST_PARAMETER_TYPE_NOT_MATCH_ERROR; +import jakarta.persistence.PersistenceException; import jakarta.transaction.TransactionalException; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; @@ -50,7 +51,7 @@ protected ResponseEntity handleBusinessException(final BusinessEx protected ResponseEntity handleRequestArgumentNotValidException( MethodArgumentNotValidException e ) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); final ErrorResponse response = ErrorResponse.ofBindingResult(INPUT_INVALID_VALUE_ERROR, e.getBindingResult()); @@ -62,7 +63,7 @@ protected ResponseEntity handleRequestArgumentNotValidException( protected ResponseEntity handleRequestTypeNotValidException( HttpMessageNotReadableException e ) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); final ErrorResponse response = ErrorResponse.fromErrorCode(INPUT_INVALID_TYPE_ERROR); return ResponseEntity.status(INPUT_INVALID_VALUE_ERROR.getStatus()) @@ -73,7 +74,7 @@ protected ResponseEntity handleRequestTypeNotValidException( protected ResponseEntity handleNullCheckValidException( NullCheckValidateException e ) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); final ErrorResponse response = ErrorResponse.fromParameter( REQUEST_PARAMETER_NOT_FOUND_ERROR, e.getMessage()); @@ -83,7 +84,7 @@ protected ResponseEntity handleNullCheckValidException( @ExceptionHandler(ExternalApiException.class) protected ResponseEntity handleExternalApiException(ExternalApiException e) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); final ErrorCode errorCode = e.getErrorCode(); final ErrorResponse response = ErrorResponse.fromErrorCode(errorCode); @@ -96,7 +97,7 @@ protected ResponseEntity handleExternalApiException(ExternalApiEx protected ResponseEntity handleMissingServletRequestParameterException( MissingServletRequestParameterException e ) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); final ErrorResponse errorResponse = ErrorResponse.fromParameter( REQUEST_PARAMETER_NOT_FOUND_ERROR, @@ -110,7 +111,7 @@ protected ResponseEntity handleMissingServletRequestParameterExce protected ResponseEntity handleMethodArgumentTypeMismatchException( MethodArgumentTypeMismatchException e ) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); final ErrorResponse errorResponse = ErrorResponse.fromType( REQUEST_PARAMETER_TYPE_NOT_MATCH_ERROR, @@ -126,7 +127,7 @@ protected ResponseEntity handleMethodArgumentTypeMismatchExceptio protected ResponseEntity handleDataIntegrityViolationException( DataIntegrityViolationException e ) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); ErrorCode errorCode = INPUT_INVALID_VALUE_ERROR; final ErrorResponse errorResponse = ErrorResponse.fromErrorCode(errorCode); @@ -137,7 +138,7 @@ protected ResponseEntity handleDataIntegrityViolationException( @ExceptionHandler(TransactionalException.class) protected ResponseEntity handleTransactionException(TransactionalException e) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); ErrorCode errorCode = INTERNAL_SERVER_ERROR; final ErrorResponse errorResponse = ErrorResponse.fromErrorCode(errorCode); @@ -149,7 +150,7 @@ protected ResponseEntity handleTransactionException(Transactional @ExceptionHandler(ConstraintViolationException.class) protected ResponseEntity handleConstraintViolationException( ConstraintViolationException e) { - log.warn(e.getMessage(), e); + log.error(e.getMessage(), e); ErrorCode errorCode = INPUT_INVALID_VALUE_ERROR; final ErrorResponse errorResponse = ErrorResponse.ofConstraints(errorCode, @@ -158,4 +159,15 @@ protected ResponseEntity handleConstraintViolationException( return ResponseEntity.status(errorCode.getStatus()) .body(errorResponse); } + + @ExceptionHandler(PersistenceException.class) + protected ResponseEntity handlePersistenceException(PersistenceException e) { + log.error(e.getMessage(), e); + + ErrorCode errorCode = INTERNAL_SERVER_ERROR; + final ErrorResponse errorResponse = ErrorResponse.fromErrorCode(errorCode); + + return ResponseEntity.status(errorCode.getStatus()) + .body(errorResponse); + } } From ee4903276608985f1a1f7e4c097dda55a61a6ad4 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 2 Jun 2024 14:46:10 +0900 Subject: [PATCH 090/195] =?UTF-8?q?feat=20:=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B8=B0=EC=A1=B4=20API=20?= =?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=84=B1,=20=EC=83=81=ED=83=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- .../data/dto/FriendInviteMemberIdsDto.java | 28 +++++ .../FriendInviteDuplicateException.java | 11 ++ ...java => SelfFriendOperationException.java} | 6 +- .../FriendInviteQueryRepository.java | 6 ++ .../FriendInviteQueryRepositoryImpl.java | 53 +++++++++ .../friend_invite/FriendInviteRepository.java | 34 +++--- .../service/command/FriendCommandService.java | 102 ++++++++++-------- .../core/global/error/ErrorCode.java | 5 +- 9 files changed, 179 insertions(+), 68 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/FriendInviteDuplicateException.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/{FriendDuplicateIdException.java => SelfFriendOperationException.java} (55%) diff --git a/.gitignore b/.gitignore index 91cd78597..51e94bb56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea -data \ No newline at end of file +backend/data \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java new file mode 100644 index 000000000..65f6132f1 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java @@ -0,0 +1,28 @@ +package site.timecapsulearchive.core.domain.friend.data.dto; + +import java.util.Objects; + +public record FriendInviteMemberIdsDto( + Long ownerId, + Long friendId +) { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FriendInviteMemberIdsDto that = (FriendInviteMemberIdsDto) o; + return (Objects.equals(ownerId, that.ownerId) && Objects.equals(friendId, that.friendId)) + || (Objects.equals(ownerId, that.friendId) && Objects.equals(friendId, that.ownerId)); + } + + @Override + public int hashCode() { + return Objects.hash(ownerId, friendId); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/FriendInviteDuplicateException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/FriendInviteDuplicateException.java new file mode 100644 index 000000000..e9963080b --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/FriendInviteDuplicateException.java @@ -0,0 +1,11 @@ +package site.timecapsulearchive.core.domain.friend.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.global.error.exception.BusinessException; + +public class FriendInviteDuplicateException extends BusinessException { + + public FriendInviteDuplicateException() { + super(ErrorCode.FRIEND_INVITE_DUPLICATE_ERROR); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/FriendDuplicateIdException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/SelfFriendOperationException.java similarity index 55% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/FriendDuplicateIdException.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/SelfFriendOperationException.java index d221257d9..eba202929 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/FriendDuplicateIdException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/exception/SelfFriendOperationException.java @@ -3,9 +3,9 @@ import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.BusinessException; -public class FriendDuplicateIdException extends BusinessException { +public class SelfFriendOperationException extends BusinessException { - public FriendDuplicateIdException() { - super(ErrorCode.FRIEND_DUPLICATE_ID_ERROR); + public SelfFriendOperationException() { + super(ErrorCode.SELF_FRIEND_OPERATION_ERROR); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java index d8887fb0d..aec48ac1d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java @@ -1,8 +1,14 @@ package site.timecapsulearchive.core.domain.friend.repository.friend_invite; import java.util.List; +import java.util.Optional; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteMemberIdsDto; public interface FriendInviteQueryRepository { void bulkSave(final Long ownerId, final List friendIds); + + List findFriendInviteMemberIdsDtoByMemberIdsAndFriendId(List memberIds, Long friendId); + + Optional findFriendInviteMemberIdsDtoByMemberIdAndFriendId(Long memberId, Long friendId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java index 136441e2d..8c156acf0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java @@ -1,21 +1,29 @@ package site.timecapsulearchive.core.domain.friend.repository.friend_invite; +import static site.timecapsulearchive.core.domain.friend.entity.QFriendInvite.friendInvite; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteMemberIdsDto; @Repository @RequiredArgsConstructor public class FriendInviteQueryRepositoryImpl implements FriendInviteQueryRepository { private final JdbcTemplate jdbcTemplate; + private final JPAQueryFactory jpaQueryFactory; public void bulkSave(final Long ownerId, final List friendIds) { if (friendIds.isEmpty()) { @@ -47,4 +55,49 @@ public int getBatchSize() { } ); } + + @Override + public List findFriendInviteMemberIdsDtoByMemberIdsAndFriendId( + List memberIds, Long friendId) { + BooleanBuilder multipleColumnsInCondition = new BooleanBuilder(); + for (Long memberId : memberIds) { + multipleColumnsInCondition.or(friendInvite.owner.id.eq(memberId) + .and(friendInvite.friend.id.eq(friendId))); + + multipleColumnsInCondition.or(friendInvite.owner.id.eq(friendId) + .and(friendInvite.friend.id.eq(memberId))); + } + + return jpaQueryFactory + .select( + Projections.constructor( + FriendInviteMemberIdsDto.class, + friendInvite.owner.id, + friendInvite.friend.id + ) + ) + .from(friendInvite) + .where(multipleColumnsInCondition) + .fetch(); + } + + @Override + public Optional findFriendInviteMemberIdsDtoByMemberIdAndFriendId( + Long memberId, Long friendId) { + return Optional.ofNullable( + jpaQueryFactory + .select( + Projections.constructor( + FriendInviteMemberIdsDto.class, + friendInvite.owner.id, + friendInvite.friend.id + ) + ) + .from(friendInvite) + .where(friendInvite.owner.id.eq(memberId).and(friendInvite.friend.id.eq(friendId)) + .or(friendInvite.owner.id.eq(friendId) + .and(friendInvite.friend.id.eq(memberId)))) + .fetchOne() + ); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java index 562710440..e37380046 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java @@ -16,25 +16,25 @@ public interface FriendInviteRepository extends Repository, void save(FriendInvite friendInvite); - @Query(value = "select fi " - + "from FriendInvite fi " - + "join fetch fi.owner " - + "join fetch fi.friend " - + "where (fi.owner.id =:friendId and fi.friend.id =:memberId) " - + "or (fi.owner.id =: memberId and fi.friend.id =: friendId)") - List findFriendInviteWithMembersByOwnerIdAndFriendId( - @Param(value = "memberId") Long memberId, - @Param(value = "friendId") Long friendId - ); - - Optional findFriendInviteByOwnerIdAndFriendId(Long memberId, Long targetId); - - int deleteFriendInviteByOwnerIdAndFriendId(Long memberId, Long targetId); - void delete(FriendInvite friendInvite); @Lock(LockModeType.READ) - @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "3000")}) - Optional findFriendInviteForUpdateByOwnerIdAndFriendId(Long memberId, Long targetId); + @QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")}) + Optional findFriendSendingInviteForUpdateByOwnerIdAndFriendId(Long memberId, + Long friendId); + + @Query(value = """ + select fi + from FriendInvite fi + join fetch fi.owner + join fetch fi.friend + where fi.owner.id =:friendId and fi.friend.id =:memberId + """) + @Lock(LockModeType.READ) + @QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")}) + Optional findFriendReceptionInviteForUpdateByOwnerIdAndFriendId( + @Param(value = "memberId") Long memberId, + @Param(value = "friendId") Long friendId + ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java index 8d153cce5..8c4d885a4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java @@ -1,6 +1,5 @@ package site.timecapsulearchive.core.domain.friend.service.command; -import jakarta.persistence.PersistenceException; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -10,16 +9,19 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteMemberIdsDto; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; -import site.timecapsulearchive.core.domain.friend.exception.FriendDuplicateIdException; +import site.timecapsulearchive.core.domain.friend.exception.FriendInviteDuplicateException; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; import site.timecapsulearchive.core.domain.friend.exception.FriendTwoWayInviteException; +import site.timecapsulearchive.core.domain.friend.exception.SelfFriendOperationException; import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteRepository; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; 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; @Slf4j @@ -34,6 +36,12 @@ public class FriendCommandService { private final TransactionTemplate transactionTemplate; public void requestFriends(Long memberId, List friendIds) { + List filteredFriendIds = filterTwoWayAndDuplicateInvite(memberId, friendIds); + + if (filteredFriendIds.isEmpty()) { + return; + } + final Member[] owner = new Member[1]; final List[] foundFriendIds = new List[1]; @@ -42,7 +50,7 @@ public void requestFriends(Long memberId, List friendIds) { protected void doInTransactionWithoutResult(TransactionStatus status) { owner[0] = memberRepository.findMemberById(memberId) .orElseThrow(MemberNotFoundException::new); - foundFriendIds[0] = memberRepository.findMemberIdsByIds(friendIds); + foundFriendIds[0] = memberRepository.findMemberIdsByIds(filteredFriendIds); friendInviteRepository.bulkSave(owner[0].getId(), foundFriendIds[0]); } @@ -55,9 +63,19 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { ); } + private List filterTwoWayAndDuplicateInvite(Long memberId, List friendIds) { + List twoWayIds = friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId( + friendIds, memberId); + + return friendIds.stream() + .filter(id -> !memberId.equals(id)) + .filter(id -> !twoWayIds.contains(new FriendInviteMemberIdsDto(id, memberId))) + .toList(); + } + public void requestFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); - validateTwoWayInvite(memberId, friendId); + validateSelfFriendOperation(memberId, friendId); + validateTwoWayAndDuplicateInvite(memberId, friendId); final Member owner = memberRepository.findMemberById(memberId).orElseThrow( MemberNotFoundException::new); @@ -77,67 +95,61 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { socialNotificationManager.sendFriendReqMessage(owner.getNickname(), friendId); } - private void validateFriendDuplicateId(final Long memberId, final Long friendId) { + private void validateSelfFriendOperation(final Long memberId, final Long friendId) { if (memberId.equals(friendId)) { - throw new FriendDuplicateIdException(); + throw new SelfFriendOperationException(); } } - private void validateTwoWayInvite(final Long memberId, final Long friendId) { - final Optional friendInvite = friendInviteRepository.findFriendInviteByOwnerIdAndFriendId( - friendId, memberId); + private void validateTwoWayAndDuplicateInvite(final Long memberId, final Long friendId) { + final Optional friendInvite = friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdAndFriendId( + memberId, friendId); - if (friendInvite.isPresent()) { - throw new FriendTwoWayInviteException(); - } + friendInvite.ifPresent(dto -> { + if (dto.ownerId().equals(friendId) && dto.friendId().equals(memberId)) { + throw new FriendTwoWayInviteException(); + } else { + throw new FriendInviteDuplicateException(); + } + }); } public void acceptFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); + validateSelfFriendOperation(memberId, friendId); - final String[] ownerNickname = new String[1]; - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - final List friendInvites = friendInviteRepository - .findFriendInviteWithMembersByOwnerIdAndFriendId(memberId, friendId); - - if (friendInvites.isEmpty()) { - throw new FriendInviteNotFoundException(); - } - - final FriendInvite friendInvite = friendInvites.get(0); + final String ownerNickname = transactionTemplate.execute(status -> { + FriendInvite friendInvite = friendInviteRepository.findFriendReceptionInviteForUpdateByOwnerIdAndFriendId( + memberId, friendId) + .orElseThrow(FriendInviteNotFoundException::new); - final MemberFriend ownerRelation = friendInvite.ownerRelation(); - ownerNickname[0] = ownerRelation.getOwnerNickname(); + final MemberFriend ownerRelation = friendInvite.ownerRelation(); + final MemberFriend friendRelation = friendInvite.friendRelation(); - final MemberFriend friendRelation = friendInvite.friendRelation(); + memberFriendRepository.save(ownerRelation); + memberFriendRepository.save(friendRelation); + friendInviteRepository.delete(friendInvite); - memberFriendRepository.save(ownerRelation); - memberFriendRepository.save(friendRelation); - friendInvites.forEach(friendInviteRepository::delete); - } + return ownerRelation.getOwnerNickname(); }); - socialNotificationManager.sendFriendAcceptMessage(ownerNickname[0], friendId); + socialNotificationManager.sendFriendAcceptMessage(ownerNickname, friendId); } @Transactional public void denyRequestFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); + validateSelfFriendOperation(memberId, friendId); - int isDenyRequest = friendInviteRepository.deleteFriendInviteByOwnerIdAndFriendId(friendId, - memberId); + FriendInvite friendInvite = friendInviteRepository.findFriendReceptionInviteForUpdateByOwnerIdAndFriendId( + memberId, friendId) + .orElseThrow(FriendInviteNotFoundException::new); - if (isDenyRequest != 1) { - throw new FriendTwoWayInviteException(); - } + friendInviteRepository.delete(friendInvite); } @Transactional public void deleteFriend(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); + validateSelfFriendOperation(memberId, friendId); final List memberFriends = memberFriendRepository .findMemberFriendByOwnerIdAndFriendId(memberId, friendId); @@ -147,11 +159,11 @@ public void deleteFriend(final Long memberId, final Long friendId) { @Transactional public void deleteSendingFriendInvite(final Long memberId, final Long friendId) { - validateFriendDuplicateId(memberId, friendId); + validateSelfFriendOperation(memberId, friendId); - FriendInvite friendInvite = friendInviteRepository.findFriendInviteForUpdateByOwnerIdAndFriendId( - memberId, friendId) - .orElseThrow(FriendInviteNotFoundException::new); + FriendInvite friendInvite = friendInviteRepository.findFriendSendingInviteForUpdateByOwnerIdAndFriendId( + memberId, friendId) + .orElseThrow(FriendInviteNotFoundException::new); friendInviteRepository.delete(friendInvite); } 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 2f97178f9..1f3485e2e 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 @@ -56,7 +56,7 @@ public enum ErrorCode { //friend FRIEND_NOT_FOUND_ERROR(404, "FRIEND-001", "친구를 찾지 못하였습니다"), - FRIEND_DUPLICATE_ID_ERROR(404, "FRIEND-002", "친구 아이디가 중복되었습니다."), + SELF_FRIEND_OPERATION_ERROR(400, "FRIEND-002", "자기 자신을 향해 친구와 관련된 작업을 수행할 수 없습니다."), //group GROUP_CREATE_ERROR(400, "GROUP-001", "그룹 생성에 실패하였습니다."), @@ -72,7 +72,8 @@ public enum ErrorCode { //friend invite FRIEND_INVITE_NOT_FOUND_ERROR(404, "FRIEND-INVITE-001", "친구 요청을 찾지 못하였습니다."), - FRIEND_TWO_WAY_INVITE_ERROR(400, "FRIEND-INVITE-002", "친구 요청을 받은 상태입니다."); + FRIEND_TWO_WAY_INVITE_ERROR(400, "FRIEND-INVITE-002", "친구 요청을 받은 상태입니다."), + FRIEND_INVITE_DUPLICATE_ERROR(400, "FRIEND-INVITE-003", "이미 친구 요청된 상태입니다."); private final int status; private final String code; From 0207f90b806b34fbd7adb9e3607215498bb33cb2 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 2 Jun 2024 15:31:41 +0900 Subject: [PATCH 091/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/FriendInviteMemberIdsDtoFixture.java | 41 ++++ .../FriendInviteQueryRepositoryTest.java | 35 ++- .../MemberFriendQueryRepositoryTest.java | 4 +- .../MemberFriendRepositoryTest.java | 3 +- .../command/FriendCommandServiceTest.java | 200 ++++++++++++++++++ .../{ => query}/FriendQueryServiceTest.java | 4 +- 6 files changed, 274 insertions(+), 13 deletions(-) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendInviteMemberIdsDtoFixture.java rename backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/{ => friend_invite}/FriendInviteQueryRepositoryTest.java (64%) rename backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/{ => member_friend}/MemberFriendQueryRepositoryTest.java (98%) rename backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/{ => member_friend}/MemberFriendRepositoryTest.java (94%) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java rename backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/{ => query}/FriendQueryServiceTest.java (94%) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendInviteMemberIdsDtoFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendInviteMemberIdsDtoFixture.java new file mode 100644 index 000000000..beed3025d --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendInviteMemberIdsDtoFixture.java @@ -0,0 +1,41 @@ +package site.timecapsulearchive.core.common.fixture.dto; + +import java.util.List; +import java.util.Optional; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteMemberIdsDto; + +public class FriendInviteMemberIdsDtoFixture { + + public static List duplicates( + final Long memberId, + final List friendIds + ) { + return friendIds.stream() + .map(friendId -> new FriendInviteMemberIdsDto(memberId, friendId)) + .toList(); + } + + public static List twoWays( + final Long memberId, + final List friendIds + ) { + return friendIds.stream() + .map(friendId -> new FriendInviteMemberIdsDto(friendId, memberId)) + .toList(); + } + + public static Optional duplicate( + final Long memberId, + final Long friendId + ) { + return Optional.of( + new FriendInviteMemberIdsDto(memberId, friendId) + ); + } + + public static Optional twoWay(Long memberId, Long friendId) { + return Optional.of( + new FriendInviteMemberIdsDto(friendId, memberId) + ); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryTest.java similarity index 64% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryTest.java index c4c63562f..fd7a21c05 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryTest.java @@ -1,7 +1,8 @@ -package site.timecapsulearchive.core.domain.friend.repository; +package site.timecapsulearchive.core.domain.friend.repository.friend_invite; import static org.assertj.core.api.Assertions.assertThat; +import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import java.util.ArrayList; @@ -12,10 +13,10 @@ import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; import site.timecapsulearchive.core.common.RepositoryTest; +import site.timecapsulearchive.core.common.fixture.domain.FriendInviteFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteMemberIdsDto; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; -import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteQueryRepository; -import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteQueryRepositoryImpl; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) @@ -27,9 +28,11 @@ class FriendInviteQueryRepositoryTest extends RepositoryTest { private Member owner; - FriendInviteQueryRepositoryTest(EntityManager entityManager, JdbcTemplate jdbcTemplate) { + FriendInviteQueryRepositoryTest(EntityManager entityManager, JdbcTemplate jdbcTemplate, + JPAQueryFactory jpaQueryFactory) { this.entityManager = entityManager; - this.friendInviteQueryRepository = new FriendInviteQueryRepositoryImpl(jdbcTemplate); + this.friendInviteQueryRepository = new FriendInviteQueryRepositoryImpl(jdbcTemplate, + jpaQueryFactory); } @BeforeEach @@ -39,6 +42,11 @@ void setup() { friends.addAll(MemberFixture.members(1, 11)); friends.forEach(entityManager::persist); + + for (Member friend : friends) { + FriendInvite friendInvite = FriendInviteFixture.friendInvite(friend, owner); + entityManager.persist(friendInvite); + } } @Test @@ -63,4 +71,21 @@ private List getFriendInvites(EntityManager entityManager, Long ow query.setParameter("ownerId", ownerId); return query.getResultList(); } + + //Friend -> Owner + @Test + void 친구가_사용자에게_요청을_보낸_경우_사용자_아이디와_친구_아이디_목록으로_모든_요청_방향의_친구_초대를_조회하면_존재하는_단방향_친구_초대가_나온다() { + //given + List friendIds = friends.stream() + .map(Member::getId) + .toList(); + + //when + List friendInviteMemberIdsDtos = friendInviteQueryRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId( + friendIds, + owner.getId() + ); + + assertThat(friendInviteMemberIdsDtos).hasSize(friendIds.size()); + } } \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryTest.java similarity index 98% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryTest.java index 09ae4cf3d..5dec0970d 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryTest.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.repository; +package site.timecapsulearchive.core.domain.friend.repository.member_friend; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -33,8 +33,6 @@ import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; -import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendQueryRepository; -import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendQueryRepositoryImpl; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendRepositoryTest.java similarity index 94% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepositoryTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendRepositoryTest.java index 2a449daab..33a226749 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendRepositoryTest.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.repository; +package site.timecapsulearchive.core.domain.friend.repository.member_friend; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -15,7 +15,6 @@ import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFriendFixture; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; -import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java new file mode 100644 index 000000000..e1ff42ee8 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java @@ -0,0 +1,200 @@ +package site.timecapsulearchive.core.domain.friend.service.command; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.util.Collections; +import java.util.List; +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.dto.FriendInviteMemberIdsDtoFixture; +import site.timecapsulearchive.core.domain.friend.exception.FriendInviteDuplicateException; +import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; +import site.timecapsulearchive.core.domain.friend.exception.FriendTwoWayInviteException; +import site.timecapsulearchive.core.domain.friend.exception.SelfFriendOperationException; +import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteRepository; +import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.global.error.ErrorCode; +import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; + +class FriendCommandServiceTest { + + private final MemberFriendRepository memberFriendRepository = mock( + MemberFriendRepository.class); + private final MemberRepository memberRepository = mock(MemberRepository.class); + private final FriendInviteRepository friendInviteRepository = mock( + FriendInviteRepository.class); + private final SocialNotificationManager socialNotificationManager = mock( + SocialNotificationManager.class); + private final TransactionTemplate transactionTemplate = TestTransactionTemplate.spied(); + + private final FriendCommandService friendCommandService = new FriendCommandService( + memberFriendRepository, + memberRepository, + friendInviteRepository, + socialNotificationManager, + transactionTemplate + ); + + @Test + void 사용자_본인한테_다건_친구_요청을_보낸_경우_어떠한_동작도_수행하지_않는다() { + //given + Long memberId = 1L; + List friendIds = List.of(1L, 1L, 1L, 1L); + given(friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId(anyList(), + any())) + .willReturn(Collections.emptyList()); + + //when + friendCommandService.requestFriends(memberId, friendIds); + + //then + verify(transactionTemplate, never()).execute(any()); + } + + @Test + void 이미_친구요청된_친구에게_다건_친구_요청을_보낸_경우_어떠한_동작도_수행하지_않는다() { + //given + Long memberId = 1L; + List friendIds = List.of(2L, 3L, 4L, 5L, 6L); + given(friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId(anyList(), + any())) + .willReturn(FriendInviteMemberIdsDtoFixture.duplicates(memberId, friendIds)); + + //when + friendCommandService.requestFriends(memberId, friendIds); + + //then + verify(transactionTemplate, never()).execute(any()); + } + + @Test + void 양방향_다건_친구_요청을_보낸_경우_어떠한_동작도_수행하지_않는다() { + //given + Long memberId = 1L; + List friendIds = List.of(2L, 3L, 4L, 5L, 6L); + given(friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId(anyList(), + any())) + .willReturn(FriendInviteMemberIdsDtoFixture.twoWays(memberId, friendIds)); + + //when + friendCommandService.requestFriends(memberId, friendIds); + + //then + verify(transactionTemplate, never()).execute(any()); + } + + @Test + void 사용자_본인한테_단건_친구_요청을_보낸_경우_예외가_발생한다() { + //given + Long memberId = 1L; + + //when + //then + assertThatThrownBy(() -> friendCommandService.requestFriend(memberId, memberId)) + .isInstanceOf(SelfFriendOperationException.class) + .hasMessageContaining(ErrorCode.SELF_FRIEND_OPERATION_ERROR.getMessage()); + } + + @Test + void 이미_친구요청된_친구에게_단건_친구_요청을_보낸_경우_예외가_발생한다() { + //given + Long memberId = 1L; + Long friendId = 2L; + given(friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdAndFriendId(anyLong(), anyLong())) + .willReturn(FriendInviteMemberIdsDtoFixture.duplicate(memberId, friendId)); + + //when + //then + assertThatThrownBy(() -> friendCommandService.requestFriend(memberId, friendId)) + .isInstanceOf(FriendInviteDuplicateException.class) + .hasMessageContaining(ErrorCode.FRIEND_INVITE_DUPLICATE_ERROR.getMessage()); + } + + @Test + void 양방향_단건_친구_요청을_보낸_경우_예외가_발생한다() { + //given + Long memberId = 1L; + Long friendId = 2L; + given(friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdAndFriendId(anyLong(), anyLong())) + .willReturn(FriendInviteMemberIdsDtoFixture.twoWay(memberId, friendId)); + + //when + //then + assertThatThrownBy(() -> friendCommandService.requestFriend(memberId, friendId)) + .isInstanceOf(FriendTwoWayInviteException.class) + .hasMessageContaining(ErrorCode.FRIEND_TWO_WAY_INVITE_ERROR.getMessage()); + } + + @Test + void 사용자_본인한테_친구_요청을_수락한_경우_예외가_발생한다() { + //given + Long memberId = 1L; + + //when + //then + assertThatThrownBy(() -> friendCommandService.acceptFriend(memberId, memberId)) + .isInstanceOf(SelfFriendOperationException.class) + .hasMessageContaining(ErrorCode.SELF_FRIEND_OPERATION_ERROR.getMessage()); + } + + @Test + void 사용자_본인한테_친구_요청을_거부한_경우_예외가_발생한다() { + //given + Long memberId = 1L; + + //when + //then + assertThatThrownBy(() -> friendCommandService.denyRequestFriend(memberId, memberId)) + .isInstanceOf(SelfFriendOperationException.class) + .hasMessageContaining(ErrorCode.SELF_FRIEND_OPERATION_ERROR.getMessage()); + } + + @Test + void 사용자_본인한테_친구_삭제를_요청한_경우_예외가_발생한다() { + //given + Long memberId = 1L; + + //when + //then + assertThatThrownBy(() -> friendCommandService.deleteFriend(memberId, memberId)) + .isInstanceOf(SelfFriendOperationException.class) + .hasMessageContaining(ErrorCode.SELF_FRIEND_OPERATION_ERROR.getMessage()); + } + + @Test + void 친구_요청을_보낸_사용자가_본인의_아이디로_친구_요청을_삭제하면_예외가_발생한다() { + //given + Long memberId = 1L; + + //when + //then + assertThatThrownBy(() -> friendCommandService.deleteSendingFriendInvite(memberId, memberId)) + .isInstanceOf(SelfFriendOperationException.class) + .hasMessageContaining(ErrorCode.SELF_FRIEND_OPERATION_ERROR.getMessage()); + } + + @Test + void 친구_요청을_보내지_않은_사용자가_친구_요청을_삭제하면_예외가_발생한다() { + //given + Long memberId = 1L; + Long friendId = 2L; + given(friendInviteRepository.findFriendSendingInviteForUpdateByOwnerIdAndFriendId(anyLong(), + anyLong())).willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> friendCommandService.deleteSendingFriendInvite(memberId, friendId)) + .isInstanceOf(FriendInviteNotFoundException.class) + .hasMessageContaining(ErrorCode.FRIEND_INVITE_NOT_FOUND_ERROR.getMessage()); + } +} \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java similarity index 94% rename from backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java rename to backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java index 6c940d657..db106d8c9 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.friend.service; +package site.timecapsulearchive.core.domain.friend.service.query; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -18,10 +18,8 @@ import site.timecapsulearchive.core.common.fixture.dto.FriendDtoFixture; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; -import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; -import site.timecapsulearchive.core.domain.friend.service.query.FriendQueryService; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; class FriendQueryServiceTest { From 03637d788f6a134eaaba39fba5afba692b573d7c Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 2 Jun 2024 16:00:26 +0900 Subject: [PATCH 092/195] =?UTF-8?q?fix=20:=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FriendInviteQueryRepository.java | 16 +++++ .../FriendInviteQueryRepositoryImpl.java | 68 +++++++++++++++++++ .../MemberFriendQueryRepository.java | 12 ---- .../MemberFriendQueryRepositoryImpl.java | 49 ------------- .../service/query/FriendQueryService.java | 6 +- 5 files changed, 88 insertions(+), 63 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java index d8887fb0d..2b4830a7d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java @@ -1,8 +1,24 @@ package site.timecapsulearchive.core.domain.friend.repository.friend_invite; +import java.time.ZonedDateTime; import java.util.List; +import org.springframework.data.domain.Slice; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; public interface FriendInviteQueryRepository { void bulkSave(final Long ownerId, final List friendIds); + + + Slice findFriendReceptionInvitesSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ); + + Slice findFriendSendingInvitesSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java index 136441e2d..8407a4c4f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java @@ -1,5 +1,10 @@ package site.timecapsulearchive.core.domain.friend.repository.friend_invite; +import static site.timecapsulearchive.core.domain.friend.entity.QFriendInvite.friendInvite; +import static site.timecapsulearchive.core.domain.member.entity.QMember.member; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; @@ -7,15 +12,20 @@ import java.time.ZonedDateTime; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; @Repository @RequiredArgsConstructor public class FriendInviteQueryRepositoryImpl implements FriendInviteQueryRepository { private final JdbcTemplate jdbcTemplate; + private final JPAQueryFactory jpaQueryFactory; public void bulkSave(final Long ownerId, final List friendIds) { if (friendIds.isEmpty()) { @@ -47,4 +57,62 @@ public int getBatchSize() { } ); } + + public Slice findFriendReceptionInvitesSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + final List friends = jpaQueryFactory + .select( + Projections.constructor( + FriendSummaryDto.class, + friendInvite.owner.id, + friendInvite.owner.profileUrl, + friendInvite.owner.nickname, + friendInvite.createdAt + ) + ) + .from(friendInvite) + .join(friendInvite.owner, member) + .where(friendInvite.friend.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) + .limit(size + 1) + .fetch(); + + return makeSlice(size, friends); + } + + private Slice makeSlice(int size, List dtos) { + final boolean hasNext = dtos.size() > size; + if (hasNext) { + dtos.remove(size); + } + + return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); + } + + @Override + public Slice findFriendSendingInvitesSlice( + final Long memberId, + final int size, + final ZonedDateTime createdAt + ) { + final List friends = jpaQueryFactory + .select( + Projections.constructor( + FriendSummaryDto.class, + friendInvite.friend.id, + friendInvite.friend.profileUrl, + friendInvite.friend.nickname, + friendInvite.createdAt + ) + ) + .from(friendInvite) + .join(friendInvite.owner, member) + .where(friendInvite.owner.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) + .limit(size + 1) + .fetch(); + + return makeSlice(size, friends); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java index 545cf6434..de7021d81 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java @@ -18,18 +18,6 @@ Slice findFriendsSlice( final ZonedDateTime createdAt ); - Slice findFriendReceptionInvitesSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ); - - Slice findFriendSendingInvitesSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ); - List findFriendsByPhone( final Long memberId, final List hashes diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 2b436f7cd..4e1eaa17f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -102,55 +102,6 @@ public Slice findFriendsBeforeGroupInvite( return getFriendSummaryDtos(request.size(), friends); } - public Slice findFriendReceptionInvitesSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ) { - final List friends = jpaQueryFactory - .select( - Projections.constructor( - FriendSummaryDto.class, - friendInvite.owner.id, - friendInvite.owner.profileUrl, - friendInvite.owner.nickname, - friendInvite.createdAt - ) - ) - .from(friendInvite) - .join(friendInvite.owner, member) - .where(friendInvite.friend.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) - .limit(size + 1) - .fetch(); - - return getFriendSummaryDtos(size, friends); - } - - @Override - public Slice findFriendSendingInvitesSlice( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ) { - final List friends = jpaQueryFactory - .select( - Projections.constructor( - FriendSummaryDto.class, - friendInvite.friend.id, - friendInvite.friend.profileUrl, - friendInvite.friend.nickname, - friendInvite.createdAt - ) - ) - .from(friendInvite) - .join(friendInvite.owner, member) - .where(friendInvite.owner.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) - .limit(size + 1) - .fetch(); - - return getFriendSummaryDtos(size, friends); - } - public List findFriendsByPhone( final Long memberId, final List hashes diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index 6a179fdc1..956213f12 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -11,6 +11,7 @@ import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; +import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteRepository; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @@ -20,6 +21,7 @@ public class FriendQueryService { private final MemberFriendRepository memberFriendRepository; + private final FriendInviteRepository friendInviteRepository; public Slice findFriendsSlice( final Long memberId, @@ -39,7 +41,7 @@ public Slice findFriendReceptionInvitesSlice( final int size, final ZonedDateTime createdAt ) { - return memberFriendRepository.findFriendReceptionInvitesSlice(memberId, size, createdAt); + return friendInviteRepository.findFriendReceptionInvitesSlice(memberId, size, createdAt); } public Slice findFriendSendingInvitesSlice( @@ -47,7 +49,7 @@ public Slice findFriendSendingInvitesSlice( final int size, final ZonedDateTime createdAt ) { - return memberFriendRepository.findFriendSendingInvitesSlice(memberId, size, createdAt); + return friendInviteRepository.findFriendSendingInvitesSlice(memberId, size, createdAt); } public List findFriendsByPhone( From 4d904882488b427e8e0008dcee05f48b03832e15 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 2 Jun 2024 16:00:57 +0900 Subject: [PATCH 093/195] =?UTF-8?q?fix=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FriendInviteQueryRepositoryTest.java | 207 +++++++++++++++++- .../MemberFriendQueryRepositoryTest.java | 188 +--------------- .../service/FriendQueryServiceTest.java | 6 +- 3 files changed, 211 insertions(+), 190 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java index c4c63562f..7ea22854c 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java @@ -2,17 +2,25 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.data.domain.Slice; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; import site.timecapsulearchive.core.common.RepositoryTest; +import site.timecapsulearchive.core.common.fixture.domain.FriendInviteFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteQueryRepository; import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteQueryRepositoryImpl; @@ -21,39 +29,85 @@ @TestConstructor(autowireMode = AutowireMode.ALL) class FriendInviteQueryRepositoryTest extends RepositoryTest { + private static final int MAX_COUNT = 40; + private static final Long BULK_FRIEND_INVITE_MEMBER_START_ID = 2L; + private static final Long FRIEND_RECEPTION_INVITE_MEMBER_START_ID = + BULK_FRIEND_INVITE_MEMBER_START_ID + MAX_COUNT; + private static final Long FRIEND_SENDING_INVITE_MEMBER_START_ID = + FRIEND_RECEPTION_INVITE_MEMBER_START_ID + MAX_COUNT; + private static final Long NOT_FRIEND_INVITE_START_ID = + FRIEND_SENDING_INVITE_MEMBER_START_ID + MAX_COUNT; + private final FriendInviteQueryRepository friendInviteQueryRepository; private final EntityManager entityManager; private final List friends = new ArrayList<>(); - private Member owner; + private Long bulkOwnerId; + private Long ownerId; + private Long ownerInviteReceptionStartId; + private Long ownerInviteSendingStartId; - FriendInviteQueryRepositoryTest(EntityManager entityManager, JdbcTemplate jdbcTemplate) { + FriendInviteQueryRepositoryTest(EntityManager entityManager, JdbcTemplate jdbcTemplate, + JPAQueryFactory jpaQueryFactory) { this.entityManager = entityManager; - this.friendInviteQueryRepository = new FriendInviteQueryRepositoryImpl(jdbcTemplate); + this.friendInviteQueryRepository = new FriendInviteQueryRepositoryImpl(jdbcTemplate, + jpaQueryFactory); } @BeforeEach void setup() { - owner = MemberFixture.member(0); - entityManager.persist(owner); + // 벌크 저장 시 owner 멤버 데이터 + Member bulkOwner = MemberFixture.member(0); + entityManager.persist(bulkOwner); + bulkOwnerId = bulkOwner.getId(); - friends.addAll(MemberFixture.members(1, 11)); + // 벌크 저장 시 owner 친구 데이터 + friends.addAll(MemberFixture.members(2, BULK_FRIEND_INVITE_MEMBER_START_ID.intValue())); friends.forEach(entityManager::persist); + + // 친구 초대 owner 멤버 데이터 + Member owner = MemberFixture.member(1); + entityManager.persist(owner); + ownerId = owner.getId(); + + // owner에게 친구 요청만 받은 멤버 데이터 + List receptionInviteToOwnerMembers = MemberFixture.members( + FRIEND_RECEPTION_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); + for (Member member : receptionInviteToOwnerMembers) { + entityManager.persist(member); + + FriendInvite receptionInvite = FriendInviteFixture.friendInvite(owner, member); + entityManager.persist(receptionInvite); + } + ownerInviteReceptionStartId = receptionInviteToOwnerMembers.get(0).getId(); + + // owner에게 친구 요청만 보낸 멤버 데이터 + List sendingInviteToOwnerMembers = MemberFixture.members( + FRIEND_SENDING_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); + for (Member member : sendingInviteToOwnerMembers) { + entityManager.persist(member); + + FriendInvite sendingInvite = FriendInviteFixture.friendInvite(member, owner); + entityManager.persist(sendingInvite); + } + ownerInviteSendingStartId = sendingInviteToOwnerMembers.get(0).getId(); + + entityManager.flush(); + entityManager.clear(); } @Test void 대량의_친구_초대를_저장하면_조회하면_친구_초대를_볼_수_있다() { //given - Long ownerId = owner.getId(); List friendIds = friends.stream() .map(Member::getId) .toList(); //when - friendInviteQueryRepository.bulkSave(ownerId, friendIds); + friendInviteQueryRepository.bulkSave(bulkOwnerId, friendIds); //then - List friendInvites = getFriendInvites(entityManager, ownerId); + List friendInvites = getFriendInvites(entityManager, bulkOwnerId); assertThat(friendInvites.size()).isEqualTo(friendIds.size()); } @@ -63,4 +117,139 @@ private List getFriendInvites(EntityManager entityManager, Long ow query.setParameter("ownerId", ownerId); return query.getResultList(); } + + + @ValueSource(ints = {2, 7, 10, 5}) + @ParameterizedTest + void 사용자가_보낸_친구_요청_받은_목록을_조회하면_보낸_친구_요청목록이_나온다(int size) { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + + Slice slice = friendInviteQueryRepository.findFriendSendingInvitesSlice( + ownerId, + size, + now + ); + + assertThat(slice.getContent()).isNotEmpty(); + assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteReceptionStartId + && dto.id() < ownerInviteReceptionStartId + MAX_COUNT); + } + + @Test + void 사용자가_첫_페이지_이후의_친구요청_보낸_목록을_조회하면_다음_페이지의_보낸_친구_요청목록이_나온다() { + //given + int size = 20; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + Slice firstSlice = friendInviteQueryRepository.findFriendSendingInvitesSlice( + ownerId, size, now); + + //when + FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); + Slice nextSlice = friendInviteQueryRepository.findFriendSendingInvitesSlice( + ownerId, size, dto.createdAt()); + + //then + assertThat(nextSlice.getNumberOfElements()).isPositive(); + } + + @Test + void 사용자가_유효하지_않은_시간으로_사용자의_친구_요청_보낸_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); + + //when + Slice slice = friendInviteQueryRepository.findFriendSendingInvitesSlice( + ownerId, + size, + now + ); + + //then + assertThat(slice).isEmpty(); + } + + @Test + void 친구_요청을_보내지_않은_사용자가_친구_요청_보낸_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); + + //when + Slice slice = friendInviteQueryRepository.findFriendSendingInvitesSlice( + NOT_FRIEND_INVITE_START_ID, + size, + now + ); + + //then + assertThat(slice).isEmpty(); + } + + @ValueSource(ints = {2, 7, 10, 5}) + @ParameterizedTest + void 사용자가_받은_친구_요청_받은_목록을_조회하면_받은_친구_요청목록이_나온다(int size) { + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + + Slice slice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + ownerId, + size, + now + ); + + assertThat(slice.getContent()).isNotEmpty(); + assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteSendingStartId + && dto.id() < ownerInviteSendingStartId + MAX_COUNT); + } + + @Test + void 사용자가_첫_페이지_이후의_친구요청_받은_목록을_조회하면_다음_페이지의_받은_친구_요청목록이_나온다() { + //given + int size = 20; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); + Slice firstSlice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + ownerId, size, now); + + //when + FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); + Slice nextSlice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + ownerId, size, dto.createdAt()); + + //then + assertThat(nextSlice.getNumberOfElements()).isPositive(); + } + + @Test + void 사용자가_유효하지_않은_시간으로_사용자의_친구_요청_받은_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); + + //when + Slice slice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + ownerId, + size, + now + ); + + //then + assertThat(slice).isEmpty(); + } + + @Test + void 친구_요청을_받지_않은_사용자가_친구_요청_받은_목록_조회하면_빈_리스트가_나온다() { + //given + int size = 10; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); + + //when + Slice slice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + NOT_FRIEND_INVITE_START_ID, + size, + now + ); + + //then + assertThat(slice).isEmpty(); + } } \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index 4c6cf98c0..662d030b5 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -41,13 +41,11 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private static final String PROPAGATION_REQUIRES_NEW = "PROPAGATION_REQUIRES_NEW"; - private static final int MAX_COUNT = 40; + private static final int MAX_COUNT = 10; private static final Long FRIEND_START_ID = 2L; private static final Long NOT_FRIEND_MEMBER_START_ID = FRIEND_START_ID + MAX_COUNT; - private static final Long FRIEND_RECEPTION_INVITE_MEMBER_START_ID = - NOT_FRIEND_MEMBER_START_ID + MAX_COUNT; - private static final Long FRIEND_SENDING_INVITE_MEMBER_START_ID = - FRIEND_RECEPTION_INVITE_MEMBER_START_ID + MAX_COUNT; + private static final Long FRIEND_INVITE_START_ID = + NOT_FRIEND_MEMBER_START_ID + NOT_FRIEND_MEMBER_START_ID; private final MemberFriendQueryRepository memberFriendQueryRepository; @@ -56,8 +54,6 @@ class MemberFriendQueryRepositoryTest extends RepositoryTest { private final List hashedNotFriendPhones = new ArrayList<>(); private Long ownerId; private Long friendId; - private Long ownerInviteSendingStartId; - private Long ownerInviteReceptionStartId; private String friendTag; private String notFriendTag; private String friendInviteTag; @@ -109,28 +105,13 @@ void setup(@Autowired EntityManager entityManager, //owner와 친구가 아닌 데이터 notFriendTag = notFriendMembers.get(0).getTag(); - // owner에게 친구 요청만 받은 멤버 데이터 - List receptionInviteToOwnerMembers = MemberFixture.members( - FRIEND_RECEPTION_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); - for (Member member : receptionInviteToOwnerMembers) { - entityManager.persist(member); + //owner와 친구 초대 관계 멤버 데이터 + Member friendInviteMember = MemberFixture.member(FRIEND_INVITE_START_ID.intValue()); + entityManager.persist(friendInviteMember); - FriendInvite receptionInvite = FriendInviteFixture.friendInvite(owner, member); - entityManager.persist(receptionInvite); - } - friendInviteTag = receptionInviteToOwnerMembers.get(0).getTag(); - ownerInviteReceptionStartId = receptionInviteToOwnerMembers.get(0).getId(); - - // owner에게 친구 요청만 보낸 멤버 데이터 - List sendingInviteToOwnerMembers = MemberFixture.members( - FRIEND_SENDING_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); - for (Member member : sendingInviteToOwnerMembers) { - entityManager.persist(member); - - FriendInvite sendingInvite = FriendInviteFixture.friendInvite(member, owner); - entityManager.persist(sendingInvite); - } - ownerInviteSendingStartId = sendingInviteToOwnerMembers.get(0).getId(); + FriendInvite friendInvite = FriendInviteFixture.friendInvite(owner, friendInviteMember); + entityManager.persist(friendInvite); + friendInviteTag = friendInviteMember.getTag(); }); } @@ -205,23 +186,6 @@ void clear(@Autowired EntityManager entityManager) { assertThat(slice.getContent().size()).isEqualTo(1); } - @Test - void 친구_요청만_보낸_사용자가_친구_목록_조회하면_빈_리스트가_나온다() { - //given - int size = 10; - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); - - //when - Slice slice = memberFriendQueryRepository.findFriendsSlice( - FRIEND_RECEPTION_INVITE_MEMBER_START_ID, - size, - now - ); - - //then - assertThat(slice).isEmpty(); - } - @Test void 사용자가_유효하지_않은_시간으로_사용자의_친구_목록_조회하면_빈_리스트가_나온다() { //given @@ -256,140 +220,6 @@ void clear(@Autowired EntityManager entityManager) { assertThat(slice).isEmpty(); } - @ValueSource(ints = {2, 7, 10, 5}) - @ParameterizedTest - void 사용자가_보낸_친구_요청_받은_목록을_조회하면_보낸_친구_요청목록이_나온다(int size) { - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); - - Slice slice = memberFriendQueryRepository.findFriendSendingInvitesSlice( - ownerId, - size, - now - ); - - assertThat(slice.getContent()).isNotEmpty(); - assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteReceptionStartId - && dto.id() < ownerInviteReceptionStartId + MAX_COUNT); - } - - @Test - void 사용자가_첫_페이지_이후의_친구요청_보낸_목록을_조회하면_다음_페이지의_보낸_친구_요청목록이_나온다() { - //given - int size = 20; - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); - Slice firstSlice = memberFriendQueryRepository.findFriendSendingInvitesSlice( - ownerId, size, now); - - //when - FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); - Slice nextSlice = memberFriendQueryRepository.findFriendSendingInvitesSlice( - ownerId, size, dto.createdAt()); - - //then - assertThat(nextSlice.getNumberOfElements()).isPositive(); - } - - @Test - void 사용자가_유효하지_않은_시간으로_사용자의_친구_요청_보낸_목록_조회하면_빈_리스트가_나온다() { - //given - int size = 10; - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); - - //when - Slice slice = memberFriendQueryRepository.findFriendSendingInvitesSlice( - ownerId, - size, - now - ); - - //then - assertThat(slice).isEmpty(); - } - - @Test - void 친구_요청을_보내지_않은_사용자가_친구_요청_보낸_목록_조회하면_빈_리스트가_나온다() { - //given - int size = 10; - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); - - //when - Slice slice = memberFriendQueryRepository.findFriendSendingInvitesSlice( - NOT_FRIEND_MEMBER_START_ID, - size, - now - ); - - //then - assertThat(slice).isEmpty(); - } - - @ValueSource(ints = {2, 7, 10, 5}) - @ParameterizedTest - void 사용자가_받은_친구_요청_받은_목록을_조회하면_받은_친구_요청목록이_나온다(int size) { - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); - - Slice slice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( - ownerId, - size, - now - ); - - assertThat(slice.getContent()).isNotEmpty(); - assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteSendingStartId - && dto.id() < ownerInviteSendingStartId + MAX_COUNT); - } - - @Test - void 사용자가_첫_페이지_이후의_친구요청_받은_목록을_조회하면_다음_페이지의_받은_친구_요청목록이_나온다() { - //given - int size = 20; - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); - Slice firstSlice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( - ownerId, size, now); - - //when - FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); - Slice nextSlice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( - ownerId, size, dto.createdAt()); - - //then - assertThat(nextSlice.getNumberOfElements()).isPositive(); - } - - @Test - void 사용자가_유효하지_않은_시간으로_사용자의_친구_요청_받은_목록_조회하면_빈_리스트가_나온다() { - //given - int size = 10; - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); - - //when - Slice slice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( - ownerId, - size, - now - ); - - //then - assertThat(slice).isEmpty(); - } - - @Test - void 친구_요청을_받지_않은_사용자가_친구_요청_받은_목록_조회하면_빈_리스트가_나온다() { - //given - int size = 10; - ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); - - //when - Slice slice = memberFriendQueryRepository.findFriendReceptionInvitesSlice( - NOT_FRIEND_MEMBER_START_ID, - size, - now - ); - - //then - assertThat(slice).isEmpty(); - } - @Test void 사용자가_앱_사용자의_전화번호가_아닌_경우_주소록_기반_사용자_리스트_조회하면_빈_리스트가_나온다() { //given diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java index 6c940d657..97e83bb9f 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java @@ -18,8 +18,8 @@ import site.timecapsulearchive.core.common.fixture.dto.FriendDtoFixture; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; -import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; +import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteRepository; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.domain.friend.service.query.FriendQueryService; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @@ -28,9 +28,11 @@ class FriendQueryServiceTest { private final MemberFriendRepository memberFriendRepository = mock( MemberFriendRepository.class); + private final FriendInviteRepository friendInviteRepository = mock( + FriendInviteRepository.class); private final FriendQueryService friendQueryService = new FriendQueryService( - memberFriendRepository); + memberFriendRepository, friendInviteRepository); @Test void 사용자는_주소록_기반_핸드폰_번호로_Ahchive_사용자_리스트를_조회_할_수_있다() { From 80f0db5a3eb2d2f0f7ca426747bdb80ff054fbd1 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 2 Jun 2024 18:40:45 +0900 Subject: [PATCH 094/195] =?UTF-8?q?refactor=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/query/GroupQueryApiController.java | 4 +-- ...yDto.java => CompleteGroupSummaryDto.java} | 2 +- .../data/response/GroupsSliceResponse.java | 4 +-- .../repository/GroupQueryRepository.java | 8 +++-- .../repository/GroupQueryRepositoryImpl.java | 34 +++++++------------ .../service/query/GroupQueryService.java | 29 ++++++++++++++-- .../GroupInviteQueryRepositoryImpl.java | 1 + .../MemberGroupQueryRepositoryImpl.java | 3 ++ .../MemberGroupQueryRepositoryTest.java | 20 +++++------ 9 files changed, 62 insertions(+), 43 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/{FinalGroupSummaryDto.java => CompleteGroupSummaryDto.java} (95%) 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 index dbb3a90d6..c50def105 100644 --- 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 @@ -11,7 +11,7 @@ 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.FinalGroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.CompleteGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; @@ -60,7 +60,7 @@ public ResponseEntity> findGroups( @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, + final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, size, createdAt); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/CompleteGroupSummaryDto.java similarity index 95% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/CompleteGroupSummaryDto.java index f545d91fb..80f4a9a5c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/FinalGroupSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/CompleteGroupSummaryDto.java @@ -3,7 +3,7 @@ import java.util.function.Function; import site.timecapsulearchive.core.domain.group.data.response.GroupSummaryResponse; -public record FinalGroupSummaryDto( +public record CompleteGroupSummaryDto( GroupSummaryDto groupSummaryDto, String groupOwnerProfileUrl, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java index f6174c2f7..c5cdefe02 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/response/GroupsSliceResponse.java @@ -3,7 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import java.util.function.Function; -import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.CompleteGroupSummaryDto; @Schema(description = "사용자의 그룹 목록 응답") public record GroupsSliceResponse( @@ -16,7 +16,7 @@ public record GroupsSliceResponse( ) { public static GroupsSliceResponse createOf( - final List groups, + final List groups, final Boolean hasNext, final Function preSignedUrlFunction ) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index 42a00ef9a..5929736df 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -4,19 +4,23 @@ import java.util.List; import java.util.Optional; import org.springframework.data.domain.Slice; -import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; public interface GroupQueryRepository { - Slice findGroupsSlice( + Slice findGroupSummaries( final Long memberId, final int size, final ZonedDateTime createdAt ); + List getGroupOwnerProfileUrls(final List groupIds); + + List getTotalGroupMemberCount(final List groupIds); + Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index e3df96623..552cef031 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -14,13 +14,11 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Repository; -import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; @@ -31,12 +29,13 @@ public class GroupQueryRepositoryImpl implements GroupQueryRepository { private final JPAQueryFactory jpaQueryFactory; - public Slice findGroupsSlice( + @Override + public Slice findGroupSummaries( final Long memberId, final int size, final ZonedDateTime createdAt ) { - final List groups = jpaQueryFactory + final List groupSummaryDtos = jpaQueryFactory .select( Projections.constructor( GroupSummaryDto.class, @@ -54,28 +53,16 @@ public Slice findGroupsSlice( .limit(size + 1) .fetch(); - final List groupIds = groups.stream().map(GroupSummaryDto::id).toList(); - final List groupOwnerProfileUrls = getGroupOwnerProfileUrls(groupIds); - final List totalGroupMemberCount = getTotalGroupMemberCount(groupIds); - - final boolean hasNext = groups.size() > size; + final boolean hasNext = groupSummaryDtos.size() > size; if (hasNext) { - groups.remove(size); + groupSummaryDtos.remove(size); } - final List dtos = IntStream.range(0, groups.size()) - .mapToObj(i -> new FinalGroupSummaryDto( - groups.get(i), - groupOwnerProfileUrls.get(i), - totalGroupMemberCount.get(i) - ) - ) - .toList(); - - return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); + return new SliceImpl<>(groupSummaryDtos, Pageable.ofSize(size), hasNext); } - private List getGroupOwnerProfileUrls(final List groupIds) { + @Override + public List getGroupOwnerProfileUrls(final List groupIds) { return jpaQueryFactory .select(member.profileUrl) .from(memberGroup) @@ -87,7 +74,8 @@ private List getGroupOwnerProfileUrls(final List groupIds) { .fetch(); } - private List getTotalGroupMemberCount(final List groupIds) { + @Override + public List getTotalGroupMemberCount(final List groupIds) { return jpaQueryFactory .select(memberGroup.count()) .from(memberGroup) @@ -104,6 +92,7 @@ private List getTotalGroupMemberCount(final List groupIds) { * @param memberId 사용자 아이디 * @return 그룹의 상세정보({@code memberId} 제외 그룹원) */ + @Override public Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId) { GroupDetailDto groupDetailDtoIncludeMe = @@ -162,6 +151,7 @@ public Optional findGroupDetailByGroupIdAndMemberId(final Long g ); } + @Override public Optional getTotalGroupMemberCount(final Long groupId) { return Optional.ofNullable(jpaQueryFactory .select(memberGroup.count()) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index 3cb6eba43..271b62ecc 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -2,16 +2,19 @@ import java.time.ZonedDateTime; import java.util.List; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; -import site.timecapsulearchive.core.domain.group.data.dto.FinalGroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.CompleteGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; +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; @@ -30,12 +33,32 @@ public Group findGroupById(final Long groupId) { .orElseThrow(GroupNotFoundException::new); } - public Slice findGroupsSlice( + public Slice findGroupsSlice( final Long memberId, final int size, final ZonedDateTime createdAt ) { - return groupRepository.findGroupsSlice(memberId, size, createdAt); + final Slice groupSummaryDtos = groupRepository.findGroupSummaries(memberId, + size, createdAt); + final List groupIds = groupSummaryDtos.getContent().stream().map(GroupSummaryDto::id) + .toList(); + + final List groupOwnerProfileUrls = groupRepository.getGroupOwnerProfileUrls( + groupIds); + final List totalGroupMemberCount = groupRepository.getTotalGroupMemberCount(groupIds); + + final List finalGroupSummaryDtos = IntStream.range(0, + groupSummaryDtos.getContent().size()) + .mapToObj(i -> new CompleteGroupSummaryDto( + groupSummaryDtos.getContent().get(i), + groupOwnerProfileUrls.get(i), + totalGroupMemberCount.get(i) + ) + ) + .toList(); + + return new SliceImpl<>(finalGroupSummaryDtos, groupSummaryDtos.getPageable(), + groupSummaryDtos.hasNext()); } public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final Long groupId) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index 2936216ef..ca2e6566b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -29,6 +29,7 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepositor private final JdbcTemplate jdbcTemplate; private final JPAQueryFactory jpaQueryFactory; + @Override public void bulkSave(final Long groupOwnerId, final Long groupId, final List groupMemberIds) { jdbcTemplate.batchUpdate( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index d2531cdbb..25183a043 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -18,6 +18,7 @@ public class MemberGroupQueryRepositoryImpl implements MemberGroupQueryRepositor private final JPAQueryFactory jpaQueryFactory; + @Override public Optional findOwnerInMemberGroup(final Long groupId, final Long memberId) { return Optional.ofNullable(jpaQueryFactory @@ -38,6 +39,7 @@ public Optional findOwnerInMemberGroup(final Long groupId, ); } + @Override public Optional findIsOwnerByMemberIdAndGroupId( final Long memberId, final Long groupId @@ -51,6 +53,7 @@ public Optional findIsOwnerByMemberIdAndGroupId( ); } + @Override public Optional> findGroupMemberIds(final Long groupId) { return Optional.ofNullable(jpaQueryFactory .select(memberGroup.member.id) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java index 88fb596db..eec20ba04 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/MemberGroupQueryRepositoryTest.java @@ -21,7 +21,7 @@ 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.domain.group.data.dto.FinalGroupSummaryDto; +import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepository; import site.timecapsulearchive.core.domain.group.repository.GroupQueryRepositoryImpl; @@ -86,7 +86,7 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().plusDays(3); //when - Slice groupsSlice = groupQueryRepository.findGroupsSlice(memberId, + Slice groupsSlice = groupQueryRepository.findGroupSummaries(memberId, size, now); //then @@ -100,19 +100,17 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().plusDays(3); //when - List groupsSlice = groupQueryRepository.findGroupsSlice(memberId, + List groupsSlice = groupQueryRepository.findGroupSummaries(memberId, size, now).getContent(); //then assertSoftly(softly -> { - softly.assertThat(groupsSlice).allMatch(dto -> dto.groupSummaryDto().id() != null); + softly.assertThat(groupsSlice).allMatch(dto -> dto.id() != null); softly.assertThat(groupsSlice) - .allMatch(dto -> !dto.groupSummaryDto().groupName().isBlank()); + .allMatch(dto -> !dto.groupName().isBlank()); softly.assertThat(groupsSlice) - .allMatch(dto -> !dto.groupSummaryDto().groupProfileUrl().isBlank()); - softly.assertThat(groupsSlice).allMatch(dto -> dto.groupSummaryDto().isOwner() != null); - softly.assertThat(groupsSlice).allMatch(dto -> dto.totalGroupMemberCount() != null); - softly.assertThat(groupsSlice).allMatch(dto -> !dto.groupOwnerProfileUrl().isBlank()); + .allMatch(dto -> !dto.groupProfileUrl().isBlank()); + softly.assertThat(groupsSlice).allMatch(dto -> dto.isOwner() != null); }); } @@ -123,7 +121,7 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().plusDays(3); //when - List groupsSlice = groupQueryRepository.findGroupsSlice( + List groupsSlice = groupQueryRepository.findGroupSummaries( memberIdWithNoGroup, size, now) @@ -140,7 +138,7 @@ void setup(@Autowired EntityManager entityManager) { ZonedDateTime now = ZonedDateTime.now().minusDays(5); //when - List groupsSlice = groupQueryRepository.findGroupsSlice(memberId, + List groupsSlice = groupQueryRepository.findGroupSummaries(memberId, size, now).getContent(); //then From c11969d4b5d98d2f0b52be0b63eefe1ecdb72a1d Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 2 Jun 2024 18:57:43 +0900 Subject: [PATCH 095/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=EC=9B=90?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20API=20=EC=9C=84=EC=B9=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/group/api/query/GroupQueryApi.java | 19 ------------- .../api/query/GroupQueryApiController.java | 23 ---------------- .../repository/GroupQueryRepository.java | 3 --- .../repository/GroupQueryRepositoryImpl.java | 21 --------------- .../service/query/GroupQueryService.java | 7 ----- .../api/query/MemberGroupQueryApi.java | 20 ++++++++++++++ .../query/MemberGroupQueryApiController.java | 27 +++++++++++++++++++ .../MemberGroupQueryRepository.java | 3 +++ .../MemberGroupQueryRepositoryImpl.java | 22 +++++++++++++++ .../MemberGroupRepository.java | 1 + .../service/MemberGroupQueryService.java | 8 ++++++ .../core/global/error/ErrorCode.java | 4 +-- 12 files changed, 83 insertions(+), 75 deletions(-) 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 index cba9c0e4c..42695d39b 100644 --- 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 @@ -63,23 +63,4 @@ ResponseEntity> findGroups( @Parameter(in = ParameterIn.QUERY, description = "마지막 데이터의 시간", required = true) ZonedDateTime createdAt ); - - @Operation( - summary = "그룹에 대한 그룹 멤버 조회", - description = "그룹의 그룹원들 정보를 조회한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"group"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity> findGroupMemberInfos( - Long memberId, - - @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true) - Long groupId - ); } 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 index c50def105..4eb6a53ce 100644 --- 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 @@ -75,27 +75,4 @@ public ResponseEntity> findGroups( ) ); } - - @GetMapping( - value = "/{group_id}/members", - produces = {"application/json"} - ) - @Override - public ResponseEntity> findGroupMemberInfos( - @AuthenticationPrincipal final Long memberId, - @PathVariable("group_id") final Long groupId - ) { - final List groupMemberDtos = groupQueryService.findGroupMemberInfos( - memberId, groupId); - - return ResponseEntity.ok( - ApiSpec.success( - SuccessCode.SUCCESS, - GroupMemberInfosResponse.createOf( - groupMemberDtos, - s3PreSignedUrlManager::getS3PreSignedUrlForGet - ) - ) - ); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index 5929736df..b13299a40 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -25,7 +25,4 @@ Optional findGroupDetailByGroupIdAndMemberId(final Long groupId, final Long memberId); Optional getTotalGroupMemberCount(Long groupId); - - List findGroupMemberInfos(Long memberId, Long groupId); - } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java index 552cef031..f01d8c9de 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepositoryImpl.java @@ -160,25 +160,4 @@ public Optional getTotalGroupMemberCount(final Long groupId) { .fetchOne() ); } - - @Override - public List findGroupMemberInfos( - final Long memberId, - final Long groupId - ) { - return jpaQueryFactory - .select(Projections.constructor( - GroupMemberDto.class, - member.id, - member.profileUrl, - member.nickname, - member.tag, - memberGroup.isOwner - ) - ) - .from(memberGroup) - .join(memberGroup.member, member) - .where(memberGroup.group.id.eq(groupId)) - .fetch(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java index 271b62ecc..344d3e7e0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/service/query/GroupQueryService.java @@ -74,11 +74,4 @@ public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final L return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, friendIds); } - - public List findGroupMemberInfos( - final Long memberId, - final Long groupId - ) { - return groupRepository.findGroupMemberInfos(memberId, groupId); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java index 18e84fcf6..fcd1a54eb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java @@ -8,6 +8,7 @@ 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.GroupMemberInfosResponse; import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponses; import site.timecapsulearchive.core.global.common.response.ApiSpec; @@ -36,4 +37,23 @@ ResponseEntity> findGroupInvites( ZonedDateTime createdAt ); + @Operation( + summary = "그룹에 대한 그룹 멤버 조회", + description = "그룹의 그룹원들 정보를 조회한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ) + }) + ResponseEntity> findGroupMemberInfos( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "조회할 그룹 아이디", required = true) + Long groupId + ); + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java index bbdbe2a36..a8b1c4325 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java @@ -1,14 +1,18 @@ package site.timecapsulearchive.core.domain.member_group.api.query; import java.time.ZonedDateTime; +import java.util.List; 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.GroupMemberDto; +import site.timecapsulearchive.core.domain.group.data.response.GroupMemberInfosResponse; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponses; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupQueryService; @@ -50,4 +54,27 @@ public ResponseEntity> findGroupInvites( ); } + @GetMapping( + value = "/{group_id}/members", + produces = {"application/json"} + ) + @Override + public ResponseEntity> findGroupMemberInfos( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_id") final Long groupId + ) { + final List groupMemberDtos = memberGroupQueryService.findGroupMemberInfos( + memberId, groupId); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + GroupMemberInfosResponse.createOf( + groupMemberDtos, + s3PreSignedUrlManager::getS3PreSignedUrlForGet + ) + ) + ); + } + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java index 4ec8de458..0dbb3f2a6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Optional; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; public interface MemberGroupQueryRepository { @@ -13,4 +14,6 @@ public interface MemberGroupQueryRepository { Optional> findGroupMemberIds(Long groupId); Optional findGroupOwnerId(Long groupId); + + List findGroupMemberInfos(Long memberId, Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 25183a043..0647f9dc0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -10,6 +10,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; @Repository @@ -70,4 +71,25 @@ public Optional findGroupOwnerId(final Long groupId) { .where(memberGroup.group.id.eq(groupId).and(memberGroup.isOwner.eq(true))) .fetchOne()); } + + @Override + public List findGroupMemberInfos( + final Long memberId, + final Long groupId + ) { + return jpaQueryFactory + .select(Projections.constructor( + GroupMemberDto.class, + member.id, + member.profileUrl, + member.nickname, + member.tag, + memberGroup.isOwner + ) + ) + .from(memberGroup) + .join(memberGroup.member, member) + .where(memberGroup.group.id.eq(groupId)) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java index 2d42643b7..e5970ae39 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupRepository.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Optional; import org.springframework.data.repository.Repository; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; public interface MemberGroupRepository extends Repository, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java index 848f95b95..41fba78a2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -6,6 +6,7 @@ import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; @@ -32,4 +33,11 @@ public List findGroupMemberIds(final Long groupId) { GroupNotFoundException::new); } + public List findGroupMemberInfos( + final Long memberId, + final Long groupId + ) { + return memberGroupRepository.findGroupMemberInfos(memberId, groupId); + } + } 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 13213a448..f29833617 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 @@ -37,8 +37,8 @@ public enum ErrorCode { EXTERNAL_API_ERROR(500, "EXTERNAL-001", "외부 api 호출에 실패했습니다. 잠시 후 요청해주세요."), //Redis 분산 락 - REDIS_FAILED_GET_LOCK_ERROR(403, "LOCK-001", "분산 락을 얻는데 실패 하였습니다."), - REDIS_INTERRUPT_ERROR(403, "LOCK-002", "락을 얻는데 인터럽트가 발생 하였습니다."), + REDIS_FAILED_GET_LOCK_ERROR(500, "LOCK-001", "분산 락을 얻는데 실패 하였습니다."), + REDIS_INTERRUPT_ERROR(500, "LOCK-002", "락을 얻는데 인터럽트가 발생 하였습니다."), //member LOGIN_ON_NOT_VERIFIED_ERROR(400, "MEMBER-001", "인증되지 않은 사용자로 로그인을 시도했습니다."), From 2c7119d7c60129fe1a2a4dbffb45653db6a9baff Mon Sep 17 00:00:00 2001 From: hong seokho Date: Mon, 3 Jun 2024 00:01:29 +0900 Subject: [PATCH 096/195] =?UTF-8?q?fix=20:=20=EB=9D=BD=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 순차적인 쓰기를 위한 락 모드 변경 (읽기락 -> 쓰기락) --- .../repository/friend_invite/FriendInviteRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java index e37380046..18f7bcabe 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java @@ -18,7 +18,7 @@ public interface FriendInviteRepository extends Repository, void delete(FriendInvite friendInvite); - @Lock(LockModeType.READ) + @Lock(LockModeType.PESSIMISTIC_WRITE) @QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")}) Optional findFriendSendingInviteForUpdateByOwnerIdAndFriendId(Long memberId, Long friendId); @@ -30,7 +30,7 @@ Optional findFriendSendingInviteForUpdateByOwnerIdAndFriendId(Long join fetch fi.friend where fi.owner.id =:friendId and fi.friend.id =:memberId """) - @Lock(LockModeType.READ) + @Lock(LockModeType.PESSIMISTIC_WRITE) @QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")}) Optional findFriendReceptionInviteForUpdateByOwnerIdAndFriendId( @Param(value = "memberId") Long memberId, From 9ecc5b643ff962cf4361cc9aa95ace3a5c5d383a Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 3 Jun 2024 00:24:43 +0900 Subject: [PATCH 097/195] =?UTF-8?q?refactor=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=9D=B8=EC=9B=90=20=EC=A0=9C=EC=95=BD=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트도 수정 - redis 상수 처리 추가 --- .../member/repository/MemberRepository.java | 2 -- .../MemberGroupQueryRepository.java | 2 ++ .../MemberGroupQueryRepositoryImpl.java | 10 ++++++++++ .../service/MemberGroupCommandService.java | 5 +++-- .../global/config/redis/RedissonConfig.java | 3 ++- .../config/redis/RedissonLockAspect.java | 7 ++++--- .../service/MemberGroupCommandServiceTest.java | 18 +++++++++--------- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java index 2d7d5f2bd..8efa30cea 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java @@ -17,8 +17,6 @@ public interface MemberRepository extends Repository, MemberQueryR Optional findMemberById(Long memberId); - List findMemberByIdIsIn(List memberIds); - @Modifying(clearAutomatically = true) @Query("UPDATE Member m SET m.fcmToken = :fcmToken WHERE m.id = :memberId") int updateMemberFCMToken(@Param("memberId") Long memberId, @Param("fcmToken") String fcmToken); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java index 0dbb3f2a6..e44701cb2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -16,4 +16,6 @@ public interface MemberGroupQueryRepository { Optional findGroupOwnerId(Long groupId); List findGroupMemberInfos(Long memberId, Long groupId); + + Optional findGroupMembersCount(Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 0647f9dc0..4251ae4ef 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -92,4 +92,14 @@ public List findGroupMemberInfos( .where(memberGroup.group.id.eq(groupId)) .fetch(); } + + @Override + public Optional findGroupMembersCount(final Long groupId) { + return Optional.ofNullable(jpaQueryFactory + .select(memberGroup.count()) + .from(memberGroup) + .where(memberGroup.group.id.eq(groupId)) + .fetchOne() + ); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 0f161e03b..6a791d95c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -37,8 +37,9 @@ public class MemberGroupCommandService { public void inviteGroup(final Long memberId, final SendGroupRequest sendGroupRequest) { final List friendIds = sendGroupRequest.targetIds(); - final List groupMembers = memberRepository.findMemberByIdIsIn(friendIds); - if (groupMembers.size() + friendIds.size() > 30) { + final Long groupMembersCount = memberGroupRepository.findGroupMembersCount( + sendGroupRequest.groupId()).orElseThrow(GroupNotFoundException::new); + if (groupMembersCount + friendIds.size() > 30) { throw new GroupMemberCountLimitException(); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java index 1ea17ae8f..d5ed3ec15 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonConfig.java @@ -12,13 +12,14 @@ public class RedissonConfig { private static final String REDISSON_HOST_PREFIX = "redis://"; + private static final String DIVISION = ":"; private final RedisProperties redisProperties; @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer().setAddress( - REDISSON_HOST_PREFIX + redisProperties.host() + ":" + redisProperties.port()); + REDISSON_HOST_PREFIX + redisProperties.host() + DIVISION + redisProperties.port()); return Redisson.create(config); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java index db5a32ab5..f4444c523 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java @@ -22,6 +22,7 @@ @RequiredArgsConstructor public class RedissonLockAspect { + private static final String DIVISION = ":"; private final RedissonClient redissonClient; @Around("@annotation(RedissonLock)") @@ -31,7 +32,7 @@ public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable { RedissonLock redissonLock = method.getAnnotation(RedissonLock.class); String lockKey = - method.getName() + ":" + RedisLockSpELParser.getLockKey(signature.getParameterNames(), + method.getName() + DIVISION + RedisLockSpELParser.getLockKey(signature.getParameterNames(), joinPoint.getArgs(), redissonLock.value()); long waitTime = redissonLock.waitTime(); @@ -46,12 +47,12 @@ public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable { log.info("락을 얻는데 성공하였습니다. (락 키 : {})", lockKey); return joinPoint.proceed(); } else { - log.warn("락을 얻는데 실패하였습니다. (락 키 : {})", lockKey); + log.error("락을 얻는데 실패하였습니다. (락 키 : {})", lockKey); throw new RedisLockException(ErrorCode.REDIS_FAILED_GET_LOCK_ERROR); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - log.warn("락을 얻는데 인터럽트가 발생하였습니다. (락 키 : {})", lockKey); + log.error("락을 얻는데 인터럽트가 발생하였습니다. (락 키 : {})", lockKey); throw new RedisLockException(ErrorCode.REDIS_INTERRUPT_ERROR); } finally { if (isLocked) { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index ed9d8ba60..161ca4bda 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -29,10 +29,10 @@ import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; +import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; import site.timecapsulearchive.core.domain.member_group.exception.GroupQuitException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; -import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; @@ -66,8 +66,8 @@ class MemberGroupCommandServiceTest { SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(true); - given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( - List.of(MemberFixture.member(2))); + given(memberGroupRepository.findGroupMembersCount(request.groupId())).willReturn( + Optional.of(10L)); given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn( Optional.of(groupOwnerSummaryDto)); @@ -85,8 +85,8 @@ class MemberGroupCommandServiceTest { Long memberId = 1L; SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); - given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( - MemberFixture.membersWithMemberId(1, 40)); + given(memberGroupRepository.findGroupMembersCount(request.groupId())).willReturn( + Optional.of(40L)); //when assertThatThrownBy(() -> groupMemberCommandService.inviteGroup(memberId, request)) @@ -101,8 +101,8 @@ class MemberGroupCommandServiceTest { Long memberId = 1L; SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); - given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( - List.of(MemberFixture.member(2))); + given(memberGroupRepository.findGroupMembersCount(request.groupId())).willReturn( + Optional.of(10L)); given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn( Optional.empty()); @@ -120,8 +120,8 @@ class MemberGroupCommandServiceTest { SendGroupRequest request = MemberGroupDtoFixture.sendGroupRequest(1L, List.of(2L)); GroupOwnerSummaryDto groupOwnerSummaryDto = GroupDtoFixture.groupOwnerSummaryDto(false); - given(memberRepository.findMemberByIdIsIn(request.targetIds())).willReturn( - List.of(MemberFixture.member(2))); + given(memberGroupRepository.findGroupMembersCount(request.groupId())).willReturn( + Optional.of(10L)); given(memberGroupRepository.findOwnerInMemberGroup(request.groupId(), memberId)).willReturn(Optional.of(groupOwnerSummaryDto)); From f8763c7cb526c5bc7e79ac075b5bf9ac70ed8d2e Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 3 Jun 2024 15:02:18 +0900 Subject: [PATCH 098/195] =?UTF-8?q?refactor=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=88=98=EB=9D=BD=20facade=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 큐 네트워크 연결을 제외하고 락 적용 --- .../MemberGroupCommandApiController.java | 4 +- .../data/dto/GroupAcceptNotificationDto.java | 8 ++++ .../facade/MemberGroupFacade.java | 25 +++++++++++ .../service/MemberGroupCommandService.java | 5 ++- .../MemberGroupCommandServiceTest.java | 41 ++++++++++++++++++- .../infrastructure/RedisConcurrencyTest.java | 11 ++++- 6 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupAcceptNotificationDto.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/facade/MemberGroupFacade.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java index cc8184147..1c6434a44 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; +import site.timecapsulearchive.core.domain.member_group.facade.MemberGroupFacade; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupCommandService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -20,6 +21,7 @@ public class MemberGroupCommandApiController implements MemberGroupCommandApi { private final MemberGroupCommandService memberGroupCommandService; + private final MemberGroupFacade memberGroupFacade; @DeleteMapping(value = "/{group_id}/members/quit") @Override @@ -53,7 +55,7 @@ public ResponseEntity> acceptGroupInvitation( @AuthenticationPrincipal final Long memberId, @PathVariable("group_id") final Long groupId ) { - memberGroupCommandService.acceptGroupInvite(memberId, groupId); + memberGroupFacade.acceptGroupInvite(memberId, groupId); return ResponseEntity.ok( ApiSpec.empty( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupAcceptNotificationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupAcceptNotificationDto.java new file mode 100644 index 000000000..d3f61eeb1 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupAcceptNotificationDto.java @@ -0,0 +1,8 @@ +package site.timecapsulearchive.core.domain.member_group.data.dto; + +public record GroupAcceptNotificationDto( + String groupMemberNickname, + Long groupOwnerId +) { + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/facade/MemberGroupFacade.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/facade/MemberGroupFacade.java new file mode 100644 index 000000000..9ac25e5dc --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/facade/MemberGroupFacade.java @@ -0,0 +1,25 @@ +package site.timecapsulearchive.core.domain.member_group.facade; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupAcceptNotificationDto; +import site.timecapsulearchive.core.domain.member_group.service.MemberGroupCommandService; +import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; + +@Component +@RequiredArgsConstructor +public class MemberGroupFacade { + + private final MemberGroupCommandService memberGroupCommandService; + private final SocialNotificationManager socialNotificationManager; + + public void acceptGroupInvite(final Long memberId, final Long groupId) { + final GroupAcceptNotificationDto groupAcceptNotificationDto = memberGroupCommandService.acceptGroupInvite( + memberId, groupId); + socialNotificationManager.sendGroupAcceptMessage( + groupAcceptNotificationDto.groupMemberNickname(), + groupAcceptNotificationDto.groupOwnerId()) + ; + } + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 6a791d95c..727a73fe8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -11,6 +11,7 @@ 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.domain.member_group.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; @@ -70,7 +71,7 @@ public void rejectRequestGroup(final Long memberId, final Long groupId, final Lo } @RedissonLock(value = "#groupId") - public void acceptGroupInvite(final Long memberId, final Long groupId) { + public GroupAcceptNotificationDto acceptGroupInvite(final Long memberId, final Long groupId) { final Long totalGroupMemberCount = groupRepository.getTotalGroupMemberCount(groupId) .orElseThrow(GroupNotFoundException::new); @@ -95,7 +96,7 @@ public void acceptGroupInvite(final Long memberId, final Long groupId) { memberGroupRepository.save(MemberGroup.createGroupMember(groupMember, group)); }); - socialNotificationManager.sendGroupAcceptMessage(groupMember.getNickname(), groupOwnerId); + return new GroupAcceptNotificationDto(groupMember.getNickname(), groupOwnerId); } /** diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 161ca4bda..3a56a4110 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.member_group.service; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; @@ -13,6 +14,7 @@ import java.util.List; import java.util.Optional; +import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; import org.springframework.transaction.support.TransactionTemplate; import site.timecapsulearchive.core.common.dependency.TestTransactionTemplate; @@ -25,6 +27,7 @@ import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; @@ -34,6 +37,7 @@ import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; +import site.timecapsulearchive.core.domain.member_group.facade.MemberGroupFacade; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.global.error.ErrorCode; @@ -58,6 +62,11 @@ class MemberGroupCommandServiceTest { socialNotificationManager ); + private final MemberGroupFacade groupMemberFacade = new MemberGroupFacade( + groupMemberCommandService, + socialNotificationManager + ); + @Test void 그룹장이_그룹원에게_그룹초대를_하면_그룹초대_알림이_요청된다() { @@ -184,12 +193,42 @@ class MemberGroupCommandServiceTest { groupId, 2L, memberId)).willReturn(1); //when - groupMemberCommandService.acceptGroupInvite(memberId, groupId); + groupMemberFacade.acceptGroupInvite(memberId, groupId); //then verify(socialNotificationManager, times(1)).sendGroupAcceptMessage(anyString(), anyLong()); } + @Test + void 그룹원은_그룹초대를_수락하면_알림을_보내기_위해_그룹원_이름과_그룹장_아이디를_반환한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + Member groupMember = MemberFixture.member(1); + Long groupOwnerId = 2L; + + given(groupRepository.getTotalGroupMemberCount(groupId)).willReturn(Optional.of(10L)); + given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); + given(groupRepository.findGroupById(groupId)).willReturn( + Optional.of(GroupFixture.group())); + given(memberGroupRepository.findGroupOwnerId(groupId)).willReturn(Optional.of(groupOwnerId)); + + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, groupOwnerId, memberId)).willReturn(1); + + //when + GroupAcceptNotificationDto groupAcceptNotificationDto = groupMemberCommandService.acceptGroupInvite( + memberId, groupId); + + //then + SoftAssertions.assertSoftly( + softly -> { + assertThat(groupAcceptNotificationDto.groupMemberNickname()).isEqualTo(groupMember.getNickname()); + assertThat(groupAcceptNotificationDto.groupOwnerId()).isEqualTo(groupOwnerId); + } + ); + } + @Test void 그룹원은_그룹초대를_수락할_때_그룹초대가_존재하지_않으면_예외가_발생한다() { //given diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java index 16df0a9f2..dfebd87e9 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java @@ -26,6 +26,7 @@ import site.timecapsulearchive.core.domain.group.repository.GroupRepository; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.domain.member_group.facade.MemberGroupFacade; import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupCommandService; @@ -42,6 +43,8 @@ class RedisConcurrencyTest extends RedissonTest { 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 MemberGroupCommandService groupMemberCommandService = spy( new MemberGroupCommandService( @@ -50,7 +53,11 @@ class RedisConcurrencyTest extends RedissonTest { memberGroupRepository, groupInviteRepository, TestTransactionTemplate.spied(), - mock(SocialNotificationManager.class)) + socialNotificationManager) + ); + + private final MemberGroupFacade memberGroupFacade = spy( + new MemberGroupFacade(groupMemberCommandService, socialNotificationManager) ); @@ -82,7 +89,7 @@ class RedisConcurrencyTest extends RedissonTest { for (int i = 0; i < MAX_THREADS_COUNT; i++) { executorService.submit(() -> { try { - groupMemberCommandService.acceptGroupInvite(memberId, groupId); + memberGroupFacade.acceptGroupInvite(memberId, groupId); } finally { latch.countDown(); int expectedCount = countCheck.decrementAndGet(); From 1aa32fbdea461868777dc4eb8460313e38aa9f62 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 3 Jun 2024 16:58:51 +0900 Subject: [PATCH 099/195] =?UTF-8?q?refactor=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=9D=B8=EC=9B=90=20=EC=B4=88=EA=B3=BC?= =?UTF-8?q?=EC=8B=9C=20=EA=B7=B8=EB=A3=B9=20=EC=B4=88=EB=8C=80=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=82=AD=EC=A0=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupInviteRepository.java | 13 ++++++++++-- .../service/MemberGroupCommandService.java | 21 ++++++++++++------- .../MemberGroupCommandServiceTest.java | 3 +++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java index e429c0346..009600258 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteRepository.java @@ -12,8 +12,17 @@ public interface GroupInviteRepository extends Repository, void save(GroupInvite groupInvite); - int deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId(Long groupId, Long groupOwnerId, - Long groupMemberId); + @Query("delete from GroupInvite gi " + + "where gi.group.id =:groupId " + + "and gi.groupOwner.id =:groupOwnerId " + + "and gi.groupMember.id =:groupMemberId" + ) + @Modifying + int deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + @Param("groupId") Long groupId, + @Param("groupOwnerId") Long groupOwnerId, + @Param("groupMemberId") Long groupMemberId + ); @Query("delete from GroupInvite gi where gi.id in :groupInviteIds") @Modifying diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 727a73fe8..5f9e3bbc1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -75,7 +75,11 @@ public GroupAcceptNotificationDto acceptGroupInvite(final Long memberId, final L final Long totalGroupMemberCount = groupRepository.getTotalGroupMemberCount(groupId) .orElseThrow(GroupNotFoundException::new); + final Long groupOwnerId = memberGroupRepository.findGroupOwnerId(groupId) + .orElseThrow(GroupNotFoundException::new); + if (totalGroupMemberCount == 30) { + deleteGroupInvite(memberId, groupId, groupOwnerId); throw new GroupMemberCountLimitException(); } @@ -83,22 +87,23 @@ public GroupAcceptNotificationDto acceptGroupInvite(final Long memberId, final L .orElseThrow(MemberNotFoundException::new); final Group group = groupRepository.findGroupById(groupId) .orElseThrow(GroupNotFoundException::new); - final Long groupOwnerId = memberGroupRepository.findGroupOwnerId(groupId) - .orElseThrow(GroupNotFoundException::new); transactionTemplate.executeWithoutResult(status -> { - final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( - groupId, groupOwnerId, memberId); - if (isDenyRequest != 1) { - throw new GroupInviteNotFoundException(); - } - + deleteGroupInvite(memberId, groupId, groupOwnerId); memberGroupRepository.save(MemberGroup.createGroupMember(groupMember, group)); }); return new GroupAcceptNotificationDto(groupMember.getNickname(), groupOwnerId); } + private void deleteGroupInvite(final Long memberId, final Long groupId, final Long groupOwnerId) { + final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, groupOwnerId, memberId); + if (isDenyRequest != 1) { + throw new GroupInviteNotFoundException(); + } + } + /** * 사용자는 사용자가 속한 그룹을 탈퇴한다. *
주의 - 그룹 탈퇴 시 아래 조건에 해당하면 예외가 발생한다. diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 3a56a4110..a3306a721 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -260,6 +260,9 @@ class MemberGroupCommandServiceTest { Long groupId = 1L; given(groupRepository.getTotalGroupMemberCount(groupId)).willReturn(Optional.of(30L)); + given(memberGroupRepository.findGroupOwnerId(groupId)).willReturn(Optional.of(2L)); + given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( + groupId, 2L, memberId)).willReturn(1); assertThatThrownBy( () -> groupMemberCommandService.acceptGroupInvite(memberId, groupId)) From 01c0b9c1a800b29079a66df105c52dbaefd6d4a5 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Mon, 3 Jun 2024 19:05:22 +0900 Subject: [PATCH 100/195] =?UTF-8?q?fix=20:=20loe=20->=20lt=EB=A1=9C=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 - 페이징 시 시간 조건을 loe에서 lt로 수정 --- .../friend_invite/FriendInviteQueryRepositoryImpl.java | 4 ++-- .../member_friend/MemberFriendQueryRepositoryImpl.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java index 8407a4c4f..2bd2f9261 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java @@ -75,7 +75,7 @@ public Slice findFriendReceptionInvitesSlice( ) .from(friendInvite) .join(friendInvite.owner, member) - .where(friendInvite.friend.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) + .where(friendInvite.friend.id.eq(memberId).and(friendInvite.createdAt.lt(createdAt))) .limit(size + 1) .fetch(); @@ -109,7 +109,7 @@ public Slice findFriendSendingInvitesSlice( ) .from(friendInvite) .join(friendInvite.owner, member) - .where(friendInvite.owner.id.eq(memberId).and(friendInvite.createdAt.loe(createdAt))) + .where(friendInvite.owner.id.eq(memberId).and(friendInvite.createdAt.lt(createdAt))) .limit(size + 1) .fetch(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 4e1eaa17f..63cb8d93f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -56,7 +56,7 @@ public Slice findFriendsSlice( .from(memberFriend) .innerJoin(member).on(memberFriend.owner.id.eq(member.id)) .innerJoin(member).on(memberFriend.friend.id.eq(member.id)) - .where(memberFriend.owner.id.eq(memberId).and(memberFriend.createdAt.loe(createdAt))) + .where(memberFriend.owner.id.eq(memberId).and(memberFriend.createdAt.lt(createdAt))) .limit(size + 1) .fetch(); @@ -89,7 +89,7 @@ public Slice findFriendsBeforeGroupInvite( .innerJoin(member).on(memberFriend.owner.id.eq(member.id)) .innerJoin(member).on(memberFriend.friend.id.eq(member.id)) .where(memberFriend.owner.id.eq(request.memberId()) - .and(memberFriend.createdAt.loe(request.createdAt())) + .and(memberFriend.createdAt.lt(request.createdAt())) .and(memberFriend.friend.id.notIn( select(memberGroup.member.id) .from(memberGroup) From e0d850eb3b416c73c90f6d919d21a0eb77a15652 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Mon, 3 Jun 2024 19:13:25 +0900 Subject: [PATCH 101/195] =?UTF-8?q?fix=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시간 기반 페이징의 로직 상 오류로 테스트 실패 - 아이디 기반 커서 페이징으로 변경해야 함 --- .../friend/repository/FriendInviteQueryRepositoryTest.java | 4 ++-- .../friend/repository/MemberFriendQueryRepositoryTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java index 7ea22854c..bba643e30 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java @@ -146,7 +146,7 @@ private List getFriendInvites(EntityManager entityManager, Long ow //when FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); Slice nextSlice = friendInviteQueryRepository.findFriendSendingInvitesSlice( - ownerId, size, dto.createdAt()); + ownerId, size, dto.createdAt().plusSeconds(1L)); //then assertThat(nextSlice.getNumberOfElements()).isPositive(); @@ -213,7 +213,7 @@ private List getFriendInvites(EntityManager entityManager, Long ow //when FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); Slice nextSlice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( - ownerId, size, dto.createdAt()); + ownerId, size, dto.createdAt().plusSeconds(1L)); //then assertThat(nextSlice.getNumberOfElements()).isPositive(); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java index 662d030b5..86842d9c4 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/MemberFriendQueryRepositoryTest.java @@ -164,7 +164,7 @@ void clear(@Autowired EntityManager entityManager) { Slice nextSlice = memberFriendQueryRepository.findFriendsSlice( ownerId, size, - dto.createdAt() + dto.createdAt().plusSeconds(1L) ); assertThat(nextSlice.getNumberOfElements()).isPositive(); From 6567182de2537de262da00808db4b25d29d38dc5 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Mon, 3 Jun 2024 19:17:57 +0900 Subject: [PATCH 102/195] =?UTF-8?q?fix=20:=20loe=20->=20lt=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 같은 시간인 경우 오류가 발생해 loe -> lt로 변경 --- .../group_invite_repository/GroupInviteQueryRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index c7b115a58..85465284a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -92,7 +92,7 @@ public Slice findGroupRecetpionInvitesSlice( .from(groupInvite) .join(groupInvite.group, group) .join(groupInvite.groupOwner, member) - .where(groupInvite.groupMember.id.eq(memberId).and(groupInvite.createdAt.loe(createdAt))) + .where(groupInvite.groupMember.id.eq(memberId).and(groupInvite.createdAt.lt(createdAt))) .limit(size + 1) .fetch(); From e3bac7fe7aca9bbbda5b2098eb60d1d7c288957f Mon Sep 17 00:00:00 2001 From: hong seokho Date: Mon, 3 Jun 2024 19:18:16 +0900 Subject: [PATCH 103/195] =?UTF-8?q?fix=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시간 기반 페이징의 로직 상 오류로 테스트 실패 - 아이디 기반 커서 페이징으로 변경해야 함 --- .../member_group/repository/GroupInviteQueryRepositoryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java index 1aad3c7de..65910ed1c 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -120,7 +120,7 @@ void setUp(@Autowired EntityManager entityManager) { //when GroupInviteSummaryDto dto = firstSlice.getContent().get(0); Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( - memberId, size, dto.groupReceptionInviteTime()); + memberId, size, dto.groupReceptionInviteTime().plusSeconds(1L)); //then assertThat(groupInvitesSummary.getContent()).isNotEmpty(); From 9f1dd4633d7a893de90efe736c533c02cdbae893 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Tue, 4 Jun 2024 15:15:18 +0900 Subject: [PATCH 104/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=EC=9E=A5?= =?UTF-8?q?=20preSignedUrl=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/data/dto/CompleteGroupSummaryDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/CompleteGroupSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/CompleteGroupSummaryDto.java index 80f4a9a5c..2c6b74013 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/CompleteGroupSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/CompleteGroupSummaryDto.java @@ -15,7 +15,7 @@ public GroupSummaryResponse toResponse(final Function preSignedU return GroupSummaryResponse.builder() .id(groupSummaryDto.id()) .name(groupSummaryDto.groupName()) - .groupOwnerProfileUrl(preSignedUrlFunction.apply(groupOwnerProfileUrl)) + .groupOwnerProfileUrl(groupOwnerProfileUrl) .profileUrl(preSignedUrlFunction.apply(groupSummaryDto.groupProfileUrl())) .totalGroupMemberCount(totalGroupMemberCount) .createdAt(groupSummaryDto.createdAt()) From ec350b46d53d55a37c1f7c5cc7dd69079a6c928b Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Tue, 4 Jun 2024 21:37:50 +0900 Subject: [PATCH 105/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=9A=94=EC=B2=AD=20=EC=A0=84=20=EC=B9=98?= =?UTF-8?q?=EA=B5=AC=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=B3=B4=EB=82=B8=20=EC=B9=9C=EA=B5=AC=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MemberFriendQueryRepository.java | 2 +- .../MemberFriendQueryRepositoryImpl.java | 14 +++-------- .../service/query/FriendQueryService.java | 23 ++++++++++++++++++- .../GroupInviteQueryRepository.java | 2 ++ .../GroupInviteQueryRepositoryImpl.java | 18 +++++++++++++-- .../MemberGroupQueryRepository.java | 1 + .../MemberGroupQueryRepositoryImpl.java | 8 +++++++ .../service/FriendQueryServiceTest.java | 7 ++++-- 8 files changed, 58 insertions(+), 17 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java index 8e8c32667..d2677a9f1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepository.java @@ -36,7 +36,7 @@ Optional findFriendsByTag( List findFriendIdsByOwnerId(final Long memberId); - Slice findFriendsBeforeGroupInvite( + Slice findFriends( final FriendBeforeGroupInviteRequest request); List findFriendIds(final List groupMemberIds, final Long memberId); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 7bc29fdd2..875e67d0f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -1,10 +1,8 @@ package site.timecapsulearchive.core.domain.friend.repository.member_friend; -import static com.querydsl.jpa.JPAExpressions.select; import static site.timecapsulearchive.core.domain.friend.entity.QFriendInvite.friendInvite; import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; -import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; @@ -73,8 +71,9 @@ private Slice getFriendSummaryDtos(final int size, return new SliceImpl<>(friendSummaryDtos, Pageable.ofSize(size), hasNext); } - public Slice findFriendsBeforeGroupInvite( - final FriendBeforeGroupInviteRequest request) { + public Slice findFriends( + final FriendBeforeGroupInviteRequest request + ) { final List friends = jpaQueryFactory .select( Projections.constructor( @@ -86,15 +85,8 @@ public Slice findFriendsBeforeGroupInvite( ) ) .from(memberFriend) - .innerJoin(member).on(memberFriend.owner.id.eq(member.id)) - .innerJoin(member).on(memberFriend.friend.id.eq(member.id)) .where(memberFriend.owner.id.eq(request.memberId()) .and(memberFriend.createdAt.lt(request.createdAt())) - .and(memberFriend.friend.id.notIn( - select(memberGroup.member.id) - .from(memberGroup) - .where(memberGroup.group.id.eq(request.groupId())) - )) ) .limit(request.size() + 1) .fetch(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index d34230f0a..58666e62c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -2,8 +2,10 @@ import java.time.ZonedDateTime; import java.util.List; +import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; @@ -12,6 +14,8 @@ import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @Service @@ -20,6 +24,8 @@ public class FriendQueryService { private final MemberFriendRepository memberFriendRepository; + private final MemberGroupRepository memberGroupRepository; + private final GroupInviteRepository groupInviteRepository; public Slice findFriendsSlice( final Long memberId, @@ -31,7 +37,22 @@ public Slice findFriendsSlice( public Slice findFriendsBeforeGroupInviteSlice( final FriendBeforeGroupInviteRequest request) { - return memberFriendRepository.findFriendsBeforeGroupInvite(request); + final Slice friendSummaryDtos = memberFriendRepository.findFriends( + request); + + final List groupMemberIdsToExcludeBeforeGroupInvite = Stream.concat( + memberGroupRepository.getGroupMemberIdsByGroupId(request.groupId()).stream(), + groupInviteRepository.getGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), + request.memberId()).stream()) + .distinct() + .toList(); + + final List friendSummaryBeforeGroupInvitedDtos = friendSummaryDtos.getContent() + .stream() + .filter(dto -> !groupMemberIdsToExcludeBeforeGroupInvite.contains(dto.id())).toList(); + + return new SliceImpl<>(friendSummaryBeforeGroupInvitedDtos, friendSummaryDtos.getPageable(), + friendSummaryDtos.hasNext()); } public Slice findFriendRequestsSlice( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java index 7d1725f84..bef2ad210 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepository.java @@ -16,4 +16,6 @@ Slice findGroupInvitesSummary( final int size, final ZonedDateTime createdAt ); + + List getGroupMemberIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java index ca2e6566b..c1e60cb9a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/groupInviteRepository/GroupInviteQueryRepositoryImpl.java @@ -30,8 +30,11 @@ public class GroupInviteQueryRepositoryImpl implements GroupInviteQueryRepositor private final JPAQueryFactory jpaQueryFactory; @Override - public void bulkSave(final Long groupOwnerId, final Long groupId, - final List groupMemberIds) { + public void bulkSave( + final Long groupOwnerId, + final Long groupId, + final List groupMemberIds + ) { jdbcTemplate.batchUpdate( """ INSERT INTO group_invite ( @@ -105,4 +108,15 @@ public Slice findGroupInvitesSummary( return new SliceImpl<>(groupInviteSummaryDtos, Pageable.ofSize(size), hasNext); } + @Override + public List getGroupMemberIdsByGroupIdAndGroupOwnerId( + final Long groupId, + final Long groupOwnerId + ) { + return jpaQueryFactory + .select(groupInvite.groupMember.id) + .from(groupInvite) + .where(groupInvite.groupOwner.id.eq(groupOwnerId).and(groupInvite.group.id.eq(groupId))) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java index e44701cb2..ebf8fbdb3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepository.java @@ -18,4 +18,5 @@ public interface MemberGroupQueryRepository { List findGroupMemberInfos(Long memberId, Long groupId); Optional findGroupMembersCount(Long groupId); + List getGroupMemberIdsByGroupId(final Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java index 4251ae4ef..910317fb7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/memberGroupRepository/MemberGroupQueryRepositoryImpl.java @@ -102,4 +102,12 @@ public Optional findGroupMembersCount(final Long groupId) { .fetchOne() ); } + + public List getGroupMemberIdsByGroupId(final Long groupId) { + return jpaQueryFactory + .select(memberGroup.member.id) + .from(memberGroup) + .where(memberGroup.group.id.eq(groupId)) + .fetch(); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java index 6c940d657..aad366c83 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java @@ -18,19 +18,22 @@ import site.timecapsulearchive.core.common.fixture.dto.FriendDtoFixture; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; -import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.domain.friend.service.query.FriendQueryService; +import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; class FriendQueryServiceTest { private final MemberFriendRepository memberFriendRepository = mock( MemberFriendRepository.class); + private final MemberGroupRepository memberGroupRepository = mock(MemberGroupRepository.class); + private final GroupInviteRepository groupInviteRepository = mock(GroupInviteRepository.class); private final FriendQueryService friendQueryService = new FriendQueryService( - memberFriendRepository); + memberFriendRepository, memberGroupRepository, groupInviteRepository); @Test void 사용자는_주소록_기반_핸드폰_번호로_Ahchive_사용자_리스트를_조회_할_수_있다() { From c0038d472ce835cef4ecbc7cf7488edb501dea5d Mon Sep 17 00:00:00 2001 From: hong seokho Date: Tue, 4 Jun 2024 22:54:13 +0900 Subject: [PATCH 106/195] =?UTF-8?q?fix=20:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20SliceU?= =?UTF-8?q?til=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../friend/api/query/FriendQueryApi.java | 2 +- .../api/query/FriendQueryApiController.java | 10 +++--- .../FriendInviteQueryRepository.java | 2 +- .../FriendInviteQueryRepositoryImpl.java | 16 +++------- .../service/query/FriendQueryService.java | 4 +-- .../core/global/util/SliceUtil.java | 22 +++++++++++++ .../FriendInviteQueryRepositoryTest.java | 32 +++++++++---------- 7 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/util/SliceUtil.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java index bf4fa86b9..14cd4c0d6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java @@ -79,7 +79,7 @@ ResponseEntity> findFriendsBeforeGroupInvite( description = "ok" ) }) - ResponseEntity> findFriendReceptionInvites( + ResponseEntity> findFriendReceivingInvites( Long memberId, @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java index 46077f0c9..f963efba8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApiController.java @@ -76,23 +76,23 @@ public ResponseEntity> findFriendsBeforeGroupInvit } @GetMapping( - value = "/reception-invites", + value = "/receiving-invites", produces = {"application/json"} ) @Override - public ResponseEntity> findFriendReceptionInvites( + public ResponseEntity> findFriendReceivingInvites( @AuthenticationPrincipal final Long memberId, @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice friendReceptionInvitesSlice = friendQueryService.findFriendReceptionInvitesSlice( + final Slice friendReceivingInvitesSlice = friendQueryService.findFriendReceivingInvitesSlice( memberId, size, createdAt); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - FriendsSliceResponse.createOf(friendReceptionInvitesSlice.getContent(), - friendReceptionInvitesSlice.hasNext()) + FriendsSliceResponse.createOf(friendReceivingInvitesSlice.getContent(), + friendReceivingInvitesSlice.hasNext()) ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java index 2b4830a7d..40ba418cc 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepository.java @@ -10,7 +10,7 @@ public interface FriendInviteQueryRepository { void bulkSave(final Long ownerId, final List friendIds); - Slice findFriendReceptionInvitesSlice( + Slice findFriendReceivingInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java index 2bd2f9261..ae6aeceef 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java @@ -19,6 +19,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; +import site.timecapsulearchive.core.global.util.SliceUtil; @Repository @RequiredArgsConstructor @@ -58,7 +59,7 @@ public int getBatchSize() { ); } - public Slice findFriendReceptionInvitesSlice( + public Slice findFriendReceivingInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt @@ -79,16 +80,7 @@ public Slice findFriendReceptionInvitesSlice( .limit(size + 1) .fetch(); - return makeSlice(size, friends); - } - - private Slice makeSlice(int size, List dtos) { - final boolean hasNext = dtos.size() > size; - if (hasNext) { - dtos.remove(size); - } - - return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); + return SliceUtil.makeSlice(size, friends); } @Override @@ -113,6 +105,6 @@ public Slice findFriendSendingInvitesSlice( .limit(size + 1) .fetch(); - return makeSlice(size, friends); + return SliceUtil.makeSlice(size, friends); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index 956213f12..2e0ecc5e8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -36,12 +36,12 @@ public Slice findFriendsBeforeGroupInviteSlice( return memberFriendRepository.findFriendsBeforeGroupInvite(request); } - public Slice findFriendReceptionInvitesSlice( + public Slice findFriendReceivingInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt ) { - return friendInviteRepository.findFriendReceptionInvitesSlice(memberId, size, createdAt); + return friendInviteRepository.findFriendReceivingInvitesSlice(memberId, size, createdAt); } public Slice findFriendSendingInvitesSlice( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/util/SliceUtil.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/SliceUtil.java new file mode 100644 index 000000000..be393648a --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/SliceUtil.java @@ -0,0 +1,22 @@ +package site.timecapsulearchive.core.global.util; + +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; + +public final class SliceUtil { + + private SliceUtil() { + + } + + public static Slice makeSlice(final int size, final List dtos) { + final boolean hasNext = dtos.size() > size; + if (hasNext) { + dtos.remove(size); + } + + return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java index bba643e30..8233b0761 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/FriendInviteQueryRepositoryTest.java @@ -31,10 +31,10 @@ class FriendInviteQueryRepositoryTest extends RepositoryTest { private static final int MAX_COUNT = 40; private static final Long BULK_FRIEND_INVITE_MEMBER_START_ID = 2L; - private static final Long FRIEND_RECEPTION_INVITE_MEMBER_START_ID = + private static final Long FRIEND_RECEIVING_INVITE_MEMBER_START_ID = BULK_FRIEND_INVITE_MEMBER_START_ID + MAX_COUNT; private static final Long FRIEND_SENDING_INVITE_MEMBER_START_ID = - FRIEND_RECEPTION_INVITE_MEMBER_START_ID + MAX_COUNT; + FRIEND_RECEIVING_INVITE_MEMBER_START_ID + MAX_COUNT; private static final Long NOT_FRIEND_INVITE_START_ID = FRIEND_SENDING_INVITE_MEMBER_START_ID + MAX_COUNT; @@ -44,7 +44,7 @@ class FriendInviteQueryRepositoryTest extends RepositoryTest { private Long bulkOwnerId; private Long ownerId; - private Long ownerInviteReceptionStartId; + private Long ownerInviteReceivingStartId; private Long ownerInviteSendingStartId; FriendInviteQueryRepositoryTest(EntityManager entityManager, JdbcTemplate jdbcTemplate, @@ -71,15 +71,15 @@ void setup() { ownerId = owner.getId(); // owner에게 친구 요청만 받은 멤버 데이터 - List receptionInviteToOwnerMembers = MemberFixture.members( - FRIEND_RECEPTION_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); - for (Member member : receptionInviteToOwnerMembers) { + List receivingInviteToOwnerMembers = MemberFixture.members( + FRIEND_RECEIVING_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); + for (Member member : receivingInviteToOwnerMembers) { entityManager.persist(member); - FriendInvite receptionInvite = FriendInviteFixture.friendInvite(owner, member); - entityManager.persist(receptionInvite); + FriendInvite receivingInvite = FriendInviteFixture.friendInvite(owner, member); + entityManager.persist(receivingInvite); } - ownerInviteReceptionStartId = receptionInviteToOwnerMembers.get(0).getId(); + ownerInviteReceivingStartId = receivingInviteToOwnerMembers.get(0).getId(); // owner에게 친구 요청만 보낸 멤버 데이터 List sendingInviteToOwnerMembers = MemberFixture.members( @@ -131,8 +131,8 @@ private List getFriendInvites(EntityManager entityManager, Long ow ); assertThat(slice.getContent()).isNotEmpty(); - assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteReceptionStartId - && dto.id() < ownerInviteReceptionStartId + MAX_COUNT); + assertThat(slice.getContent()).allMatch(dto -> dto.id() >= ownerInviteReceivingStartId + && dto.id() < ownerInviteReceivingStartId + MAX_COUNT); } @Test @@ -191,7 +191,7 @@ private List getFriendInvites(EntityManager entityManager, Long ow void 사용자가_받은_친구_요청_받은_목록을_조회하면_받은_친구_요청목록이_나온다(int size) { ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); - Slice slice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + Slice slice = friendInviteQueryRepository.findFriendReceivingInvitesSlice( ownerId, size, now @@ -207,12 +207,12 @@ private List getFriendInvites(EntityManager entityManager, Long ow //given int size = 20; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); - Slice firstSlice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + Slice firstSlice = friendInviteQueryRepository.findFriendReceivingInvitesSlice( ownerId, size, now); //when FriendSummaryDto dto = firstSlice.getContent().get(firstSlice.getNumberOfElements() - 1); - Slice nextSlice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + Slice nextSlice = friendInviteQueryRepository.findFriendReceivingInvitesSlice( ownerId, size, dto.createdAt().plusSeconds(1L)); //then @@ -226,7 +226,7 @@ private List getFriendInvites(EntityManager entityManager, Long ow ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(5); //when - Slice slice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + Slice slice = friendInviteQueryRepository.findFriendReceivingInvitesSlice( ownerId, size, now @@ -243,7 +243,7 @@ private List getFriendInvites(EntityManager entityManager, Long ow ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(5); //when - Slice slice = friendInviteQueryRepository.findFriendReceptionInvitesSlice( + Slice slice = friendInviteQueryRepository.findFriendReceivingInvitesSlice( NOT_FRIEND_INVITE_START_ID, size, now From 603f155bf1d13b543cd8d8e4ee62d27d63c8aae6 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Tue, 4 Jun 2024 22:59:54 +0900 Subject: [PATCH 107/195] =?UTF-8?q?fix=20:=20=EB=8B=A8=EC=96=B4=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 - 더 명확한 의미를 위해 반의어인 receiving으로 수정 --- .../api/query/MemberGroupQueryApi.java | 4 ++-- .../query/MemberGroupQueryApiController.java | 14 +++++++------- .../data/dto/GroupInviteSummaryDto.java | 10 +++++----- ...> GroupReceivingInviteSummaryResponse.java} | 10 +++++----- ...=> GroupReceivingInvitesSliceResponse.java} | 10 +++++----- .../GroupInviteQueryRepository.java | 2 +- .../GroupInviteQueryRepositoryImpl.java | 18 +++--------------- .../service/MemberGroupQueryService.java | 4 ++-- .../GroupInviteQueryRepositoryTest.java | 10 +++++----- 9 files changed, 35 insertions(+), 47 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/{GroupReceptionInviteSummaryResponse.java => GroupReceivingInviteSummaryResponse.java} (76%) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/{GroupReceptionInvitesSliceResponse.java => GroupReceivingInvitesSliceResponse.java} (64%) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java index b3fc268bd..92489c5c3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java @@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import java.time.ZonedDateTime; import org.springframework.http.ResponseEntity; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInvitesSliceResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceivingInvitesSliceResponse; import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; @@ -27,7 +27,7 @@ public interface MemberGroupQueryApi { description = "ok" ) }) - ResponseEntity> findGroupReceptionInvites( + ResponseEntity> findGroupReceivingInvites( Long memberId, @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java index 0685b9703..2d1e1e6ff 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInvitesSliceResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceivingInvitesSliceResponse; import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesResponse; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupQueryService; import site.timecapsulearchive.core.global.common.response.ApiSpec; @@ -30,24 +30,24 @@ public class MemberGroupQueryApiController implements MemberGroupQueryApi { @GetMapping( - value = "/reception-invites", + value = "/receiving-invites", produces = {"application/json"} ) @Override - public ResponseEntity> findGroupReceptionInvites( + public ResponseEntity> findGroupReceivingInvites( @AuthenticationPrincipal final Long memberId, @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice groupInviteSummarySlice = memberGroupQueryService.findGroupReceptionInvitesSlice( + final Slice groupReceivingInvitesSlice = memberGroupQueryService.findGroupReceivingInvitesSlice( memberId, size, createdAt); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - GroupReceptionInvitesSliceResponse.createOf( - groupInviteSummarySlice.getContent(), - groupInviteSummarySlice.hasNext(), + GroupReceivingInvitesSliceResponse.createOf( + groupReceivingInvitesSlice.getContent(), + groupReceivingInvitesSlice.hasNext(), s3PreSignedUrlManager::getS3PreSignedUrlForGet ) ) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java index 3d3ec6570..a5fb7fb54 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java @@ -3,7 +3,7 @@ import java.time.ZonedDateTime; import java.util.function.Function; import lombok.Builder; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceptionInviteSummaryResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceivingInviteSummaryResponse; @Builder public record GroupInviteSummaryDto( @@ -12,19 +12,19 @@ public record GroupInviteSummaryDto( String groupName, String groupProfileUrl, String description, - ZonedDateTime groupReceptionInviteTime, + ZonedDateTime groupReceivingInviteTime, String groupOwnerName ) { - public GroupReceptionInviteSummaryResponse toResponse( + public GroupReceivingInviteSummaryResponse toResponse( final Function preSignedUrlFunction ) { - return GroupReceptionInviteSummaryResponse.builder() + return GroupReceivingInviteSummaryResponse.builder() .groupId(groupId) .groupName(groupName) .groupProfileUrl(preSignedUrlFunction.apply(groupProfileUrl)) .description(description) - .groupReceptionInviteTime(groupReceptionInviteTime) + .groupReceivingInviteTime(groupReceivingInviteTime) .groupOwnerName(groupOwnerName) .build(); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInviteSummaryResponse.java similarity index 76% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInviteSummaryResponse.java index dff6de490..13d154fff 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInviteSummaryResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInviteSummaryResponse.java @@ -7,7 +7,7 @@ @Builder @Schema(description = "초대온 그룹 요약 정보") -public record GroupReceptionInviteSummaryResponse( +public record GroupReceivingInviteSummaryResponse( @Schema(description = "그룹 아이디") Long groupId, @@ -22,15 +22,15 @@ public record GroupReceptionInviteSummaryResponse( String description, @Schema(description = "그룹 초대 시간") - ZonedDateTime groupReceptionInviteTime, + ZonedDateTime groupReceivingInviteTime, @Schema(description = "그룹장") String groupOwnerName ) { - public GroupReceptionInviteSummaryResponse { - if (groupReceptionInviteTime != null) { - groupReceptionInviteTime = groupReceptionInviteTime.withZoneSameInstant( + public GroupReceivingInviteSummaryResponse { + if (groupReceivingInviteTime != null) { + groupReceivingInviteTime = groupReceivingInviteTime.withZoneSameInstant( ResponseMappingConstant.ZONE_ID); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInvitesSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInvitesSliceResponse.java similarity index 64% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInvitesSliceResponse.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInvitesSliceResponse.java index 95e5acdd8..815b84658 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceptionInvitesSliceResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInvitesSliceResponse.java @@ -5,23 +5,23 @@ import java.util.function.Function; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; -public record GroupReceptionInvitesSliceResponse( +public record GroupReceivingInvitesSliceResponse( @Schema(description = "초대 온 그룹 요약 정보 리스트") - List responses, + List responses, @Schema(description = "다음 페이지 유무") Boolean hasNext ) { - public static GroupReceptionInvitesSliceResponse createOf( + public static GroupReceivingInvitesSliceResponse createOf( final List dtos, final boolean hasNext, final Function preSignedUrlFunction ) { - List groupReceptionInviteSummaryResponses = dtos.stream() + List groupReceivingInviteSummaryResponse = dtos.stream() .map(dto -> dto.toResponse(preSignedUrlFunction)).toList(); - return new GroupReceptionInvitesSliceResponse(groupReceptionInviteSummaryResponses, + return new GroupReceivingInvitesSliceResponse(groupReceivingInviteSummaryResponse, hasNext); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java index 899883909..c9c5c3166 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java @@ -12,7 +12,7 @@ public interface GroupInviteQueryRepository { List findGroupInviteIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); - Slice findGroupRecetpionInvitesSlice( + Slice findGroupReceivingInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index 85465284a..fd3b95d2c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Repository; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.global.util.SliceUtil; @Repository @RequiredArgsConstructor @@ -72,7 +73,7 @@ public List findGroupInviteIdsByGroupIdAndGroupOwnerId( } @Override - public Slice findGroupRecetpionInvitesSlice( + public Slice findGroupReceivingInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt @@ -96,22 +97,9 @@ public Slice findGroupRecetpionInvitesSlice( .limit(size + 1) .fetch(); - return makeSlice(size, groupInviteSummaryDtos); + return SliceUtil.makeSlice(size, groupInviteSummaryDtos); } - private Slice makeSlice( - final int size, - final List dtos - ) { - final boolean hasNext = dtos.size() > size; - if (hasNext) { - dtos.remove(size); - } - - return new SliceImpl<>(dtos, Pageable.ofSize(size), hasNext); - } - - @Override public List findGroupSendingInvites( final Long memberId, final Long groupId diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java index 90b888beb..e44aa6f7f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -17,12 +17,12 @@ public class MemberGroupQueryService { private final GroupInviteRepository groupInviteRepository; - public Slice findGroupReceptionInvitesSlice( + public Slice findGroupReceivingInvitesSlice( final Long memberId, final int size, final ZonedDateTime createdAt ) { - return groupInviteRepository.findGroupRecetpionInvitesSlice(memberId, size, createdAt); + return groupInviteRepository.findGroupReceivingInvitesSlice(memberId, size, createdAt); } public List findGroupSendingInvites( diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java index 65910ed1c..5e8522e18 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -80,7 +80,7 @@ void setUp(@Autowired EntityManager entityManager) { ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); //when - Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( + Slice groupInvitesSummary = groupInviteRepository.findGroupReceivingInvitesSlice( memberId, size, createAt); //then @@ -95,7 +95,7 @@ void setUp(@Autowired EntityManager entityManager) { ZonedDateTime createAt = ZonedDateTime.now().plusDays(1); //when - Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( + Slice groupInvitesSummary = groupInviteRepository.findGroupReceivingInvitesSlice( memberId, size, createAt); //then @@ -114,13 +114,13 @@ void setUp(@Autowired EntityManager entityManager) { Long memberId = groupMemberId; int size = 1; ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(3); - Slice firstSlice = groupInviteRepository.findGroupRecetpionInvitesSlice( + Slice firstSlice = groupInviteRepository.findGroupReceivingInvitesSlice( memberId, size, now); //when GroupInviteSummaryDto dto = firstSlice.getContent().get(0); - Slice groupInvitesSummary = groupInviteRepository.findGroupRecetpionInvitesSlice( - memberId, size, dto.groupReceptionInviteTime().plusSeconds(1L)); + Slice groupInvitesSummary = groupInviteRepository.findGroupReceivingInvitesSlice( + memberId, size, dto.groupReceivingInviteTime().plusSeconds(1L)); //then assertThat(groupInvitesSummary.getContent()).isNotEmpty(); From 041cac83270eb9922c1e399a2fe4f07a0a541d4e Mon Sep 17 00:00:00 2001 From: hong seokho Date: Tue, 4 Jun 2024 23:25:15 +0900 Subject: [PATCH 108/195] =?UTF-8?q?fix=20:=20=EB=9D=BD=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 현재 상황에선 오버 엔지니어링인 것으로 예상되어 락 제거 --- .../domain/friend/entity/FriendInvite.java | 5 ++-- .../friend_invite/FriendInviteRepository.java | 15 +++-------- .../service/command/FriendCommandService.java | 22 +++++++++------- .../core/domain/member/entity/Member.java | 26 ------------------- .../global/error/GlobalExceptionHandler.java | 12 +++++++++ .../exception/InternalServerException.java | 10 +++++++ 6 files changed, 40 insertions(+), 50 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InternalServerException.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java index 9a2ce2f19..a31f7b4ae 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.friend.entity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -26,11 +27,11 @@ public class FriendInvite extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) @JoinColumn(name = "owner_id", nullable = false) private Member owner; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) @JoinColumn(name = "friend_id", nullable = false) private Member friend; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java index 18f7bcabe..bc9340551 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteRepository.java @@ -1,12 +1,7 @@ package site.timecapsulearchive.core.domain.friend.repository.friend_invite; -import jakarta.persistence.LockModeType; -import jakarta.persistence.QueryHint; -import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; @@ -18,8 +13,6 @@ public interface FriendInviteRepository extends Repository, void delete(FriendInvite friendInvite); - @Lock(LockModeType.PESSIMISTIC_WRITE) - @QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")}) Optional findFriendSendingInviteForUpdateByOwnerIdAndFriendId(Long memberId, Long friendId); @@ -28,12 +21,10 @@ Optional findFriendSendingInviteForUpdateByOwnerIdAndFriendId(Long from FriendInvite fi join fetch fi.owner join fetch fi.friend - where fi.owner.id =:friendId and fi.friend.id =:memberId + where fi.owner.id =:ownerId and fi.friend.id =:friendId """) - @Lock(LockModeType.PESSIMISTIC_WRITE) - @QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")}) - Optional findFriendReceptionInviteForUpdateByOwnerIdAndFriendId( - @Param(value = "memberId") Long memberId, + Optional findFriendReceivingInviteForUpdateByOwnerIdAndFriendId( + @Param(value = "ownerId") Long ownerId, @Param(value = "friendId") Long friendId ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java index 8c4d885a4..15a0455ad 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.friend.service.command; +import jakarta.persistence.OptimisticLockException; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -21,7 +22,7 @@ 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.global.error.exception.InternalServerException; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Slf4j @@ -115,12 +116,12 @@ private void validateTwoWayAndDuplicateInvite(final Long memberId, final Long fr } - public void acceptFriend(final Long memberId, final Long friendId) { - validateSelfFriendOperation(memberId, friendId); + public void acceptFriend(final Long memberId, final Long ownerId) { + validateSelfFriendOperation(memberId, ownerId); final String ownerNickname = transactionTemplate.execute(status -> { - FriendInvite friendInvite = friendInviteRepository.findFriendReceptionInviteForUpdateByOwnerIdAndFriendId( - memberId, friendId) + FriendInvite friendInvite = friendInviteRepository.findFriendReceivingInviteForUpdateByOwnerIdAndFriendId( + ownerId, memberId) .orElseThrow(FriendInviteNotFoundException::new); final MemberFriend ownerRelation = friendInvite.ownerRelation(); @@ -128,20 +129,21 @@ public void acceptFriend(final Long memberId, final Long friendId) { memberFriendRepository.save(ownerRelation); memberFriendRepository.save(friendRelation); + friendInviteRepository.delete(friendInvite); return ownerRelation.getOwnerNickname(); }); - socialNotificationManager.sendFriendAcceptMessage(ownerNickname, friendId); + socialNotificationManager.sendFriendAcceptMessage(ownerNickname, ownerId); } @Transactional - public void denyRequestFriend(final Long memberId, final Long friendId) { - validateSelfFriendOperation(memberId, friendId); + public void denyRequestFriend(final Long memberId, final Long ownerId) { + validateSelfFriendOperation(memberId, ownerId); - FriendInvite friendInvite = friendInviteRepository.findFriendReceptionInviteForUpdateByOwnerIdAndFriendId( - memberId, friendId) + FriendInvite friendInvite = friendInviteRepository.findFriendReceivingInviteForUpdateByOwnerIdAndFriendId( + ownerId, memberId) .orElseThrow(FriendInviteNotFoundException::new); friendInviteRepository.delete(friendInvite); 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 e94e51515..0b179f065 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 @@ -1,6 +1,5 @@ package site.timecapsulearchive.core.domain.member.entity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -8,19 +7,12 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.Email; -import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -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.history.entity.History; -import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.global.entity.BaseEntity; import site.timecapsulearchive.core.global.util.NullCheck; import site.timecapsulearchive.core.global.util.TagGenerator; @@ -74,24 +66,6 @@ public class Member extends BaseEntity { @Column(name = "tag", nullable = false, unique = true) private String tag; - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List capsules; - - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List groups; - - @OneToMany(mappedBy = "friend", cascade = CascadeType.ALL, orphanRemoval = true) - private List friends; - - @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true) - private List friendsRequests; - - @OneToMany(mappedBy = "friend", cascade = CascadeType.ALL, orphanRemoval = true) - private List notifications; - - @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) - private List histories; - @Builder private Member(String profileUrl, String nickname, SocialType socialType, String email, String authId, String password, String tag, byte[] phone, byte[] phone_hash) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java index 459f36da8..d45f86f38 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import site.timecapsulearchive.core.global.error.exception.BusinessException; +import site.timecapsulearchive.core.global.error.exception.InternalServerException; import site.timecapsulearchive.core.global.error.exception.NullCheckValidateException; import site.timecapsulearchive.core.infra.sms.exception.ExternalApiException; @@ -47,6 +48,17 @@ protected ResponseEntity handleBusinessException(final BusinessEx .body(errorResponse); } + @ExceptionHandler(InternalServerException.class) + protected ResponseEntity handleInternalServerException(final InternalServerException e) { + log.error(e.getMessage(), e); + + ErrorCode errorCode = INTERNAL_SERVER_ERROR; + final ErrorResponse errorResponse = ErrorResponse.fromErrorCode(errorCode); + + return ResponseEntity.status(errorCode.getStatus()) + .body(errorResponse); + } + @ExceptionHandler(MethodArgumentNotValidException.class) protected ResponseEntity handleRequestArgumentNotValidException( MethodArgumentNotValidException e diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InternalServerException.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InternalServerException.java new file mode 100644 index 000000000..8f8909972 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InternalServerException.java @@ -0,0 +1,10 @@ +package site.timecapsulearchive.core.global.error.exception; + +import site.timecapsulearchive.core.global.error.ErrorCode; + +public class InternalServerException extends RuntimeException { + + public InternalServerException(Throwable e) { + super(ErrorCode.INTERNAL_SERVER_ERROR.getMessage(), e); + } +} From 69d0cf6f39fa1d8422330af3753869c58f60fcc2 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Tue, 4 Jun 2024 23:32:13 +0900 Subject: [PATCH 109/195] =?UTF-8?q?fix=20:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - import 경로 수정 --- .../member_group/api/query/MemberGroupQueryApi.java | 3 +-- .../member_group/service/MemberGroupCommandService.java | 7 +++---- .../member_group/service/MemberGroupQueryService.java | 3 +-- .../service/MemberGroupCommandServiceTest.java | 8 ++++---- .../core/infrastructure/RedisConcurrencyTest.java | 4 ++-- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java index 803c81b57..9d4efaebc 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java @@ -8,10 +8,9 @@ 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.GroupMemberInfosResponse; import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceivingInvitesSliceResponse; import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesResponse; -import site.timecapsulearchive.core.domain.group.data.response.GroupMemberInfosResponse; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupInviteSummaryResponses; import site.timecapsulearchive.core.global.common.response.ApiSpec; public interface MemberGroupQueryApi { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 25cf1fe49..35dc7b94c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -20,11 +20,9 @@ import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupKickDuplicatedIdException; import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; -import site.timecapsulearchive.core.global.config.redis.RedissonLock; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; +import site.timecapsulearchive.core.global.config.redis.RedissonLock; import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Service @@ -98,7 +96,8 @@ public GroupAcceptNotificationDto acceptGroupInvite(final Long memberId, final L return new GroupAcceptNotificationDto(groupMember.getNickname(), groupOwnerId); } - private void deleteGroupInvite(final Long memberId, final Long groupId, final Long groupOwnerId) { + private void deleteGroupInvite(final Long memberId, final Long groupId, + final Long groupOwnerId) { final int isDenyRequest = groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( groupId, groupOwnerId, memberId); if (isDenyRequest != 1) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java index e62f7b264..dd8141997 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -9,10 +9,9 @@ import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; @Service @RequiredArgsConstructor diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 4f983a78c..955a6eaf4 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -38,8 +38,6 @@ import site.timecapsulearchive.core.domain.member_group.exception.MemberGroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.domain.member_group.facade.MemberGroupFacade; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.global.error.ErrorCode; @@ -213,7 +211,8 @@ class MemberGroupCommandServiceTest { given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(groupMember)); given(groupRepository.findGroupById(groupId)).willReturn( Optional.of(GroupFixture.group())); - given(memberGroupRepository.findGroupOwnerId(groupId)).willReturn(Optional.of(groupOwnerId)); + given(memberGroupRepository.findGroupOwnerId(groupId)).willReturn( + Optional.of(groupOwnerId)); given(groupInviteRepository.deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( groupId, groupOwnerId, memberId)).willReturn(1); @@ -225,7 +224,8 @@ class MemberGroupCommandServiceTest { //then SoftAssertions.assertSoftly( softly -> { - assertThat(groupAcceptNotificationDto.groupMemberNickname()).isEqualTo(groupMember.getNickname()); + assertThat(groupAcceptNotificationDto.groupMemberNickname()).isEqualTo( + groupMember.getNickname()); assertThat(groupAcceptNotificationDto.groupOwnerId()).isEqualTo(groupOwnerId); } ); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java index dfebd87e9..b86d3999b 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/infrastructure/RedisConcurrencyTest.java @@ -27,8 +27,8 @@ import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member_group.facade.MemberGroupFacade; -import site.timecapsulearchive.core.domain.member_group.repository.groupInviteRepository.GroupInviteRepository; -import site.timecapsulearchive.core.domain.member_group.repository.memberGroupRepository.MemberGroupRepository; +import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupCommandService; import site.timecapsulearchive.core.global.error.ErrorCode; import site.timecapsulearchive.core.global.error.exception.RedisLockException; From 981056a1dba1fc84048b6b6325e88d56d387ac49 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 00:03:46 +0900 Subject: [PATCH 110/195] =?UTF-8?q?fix=20:=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../friend_invite/FriendInviteQueryRepositoryImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java index c686722cd..33e02694d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java @@ -12,11 +12,13 @@ import java.sql.Types; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteMemberIdsDto; import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.global.util.SliceUtil; From b9260bedaa4df7b0a551e6629b45f8562883f276 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 00:04:05 +0900 Subject: [PATCH 111/195] =?UTF-8?q?fix=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bulk 저장 테스트와 일반 테스트 분리 --- .../FriendInviteQueryRepositoryTest.java | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryTest.java index f22eb76ee..c65fb10c0 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryTest.java @@ -5,6 +5,8 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -28,8 +30,9 @@ class FriendInviteQueryRepositoryTest extends RepositoryTest { private static final int MAX_COUNT = 40; private static final Long BULK_FRIEND_INVITE_MEMBER_START_ID = 2L; + private static final Long OWNER_START_ID = BULK_FRIEND_INVITE_MEMBER_START_ID + MAX_COUNT; private static final Long FRIEND_RECEIVING_INVITE_MEMBER_START_ID = - BULK_FRIEND_INVITE_MEMBER_START_ID + MAX_COUNT; + OWNER_START_ID + MAX_COUNT; private static final Long FRIEND_SENDING_INVITE_MEMBER_START_ID = FRIEND_RECEIVING_INVITE_MEMBER_START_ID + MAX_COUNT; private static final Long NOT_FRIEND_INVITE_START_ID = @@ -37,15 +40,21 @@ class FriendInviteQueryRepositoryTest extends RepositoryTest { private final FriendInviteQueryRepository friendInviteQueryRepository; private final EntityManager entityManager; - private final List friends = new ArrayList<>(); + + private final List bulkFriends = new ArrayList<>(); + private final List receivingInviteFriendIds = new ArrayList<>(); + private final List sendingInvitesFriendIds = new ArrayList<>(); private Long bulkOwnerId; private Long ownerId; private Long ownerInviteReceivingStartId; private Long ownerInviteSendingStartId; - FriendInviteQueryRepositoryTest(EntityManager entityManager, JdbcTemplate jdbcTemplate, - JPAQueryFactory jpaQueryFactory) { + FriendInviteQueryRepositoryTest( + EntityManager entityManager, + JdbcTemplate jdbcTemplate, + JPAQueryFactory jpaQueryFactory + ) { this.entityManager = entityManager; this.friendInviteQueryRepository = new FriendInviteQueryRepositoryImpl(jdbcTemplate, jpaQueryFactory); @@ -59,11 +68,12 @@ void setup() { bulkOwnerId = bulkOwner.getId(); // 벌크 저장 시 owner 친구 데이터 - friends.addAll(MemberFixture.members(2, BULK_FRIEND_INVITE_MEMBER_START_ID.intValue())); - friends.forEach(entityManager::persist); + bulkFriends.addAll(MemberFixture.members(BULK_FRIEND_INVITE_MEMBER_START_ID.intValue(), + BULK_FRIEND_INVITE_MEMBER_START_ID.intValue())); + bulkFriends.forEach(entityManager::persist); // 친구 초대 owner 멤버 데이터 - Member owner = MemberFixture.member(1); + Member owner = MemberFixture.member(OWNER_START_ID.intValue()); entityManager.persist(owner); ownerId = owner.getId(); @@ -72,6 +82,7 @@ void setup() { FRIEND_RECEIVING_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); for (Member member : receivingInviteToOwnerMembers) { entityManager.persist(member); + receivingInviteFriendIds.add(member.getId()); FriendInvite receivingInvite = FriendInviteFixture.friendInvite(owner, member); entityManager.persist(receivingInvite); @@ -83,6 +94,7 @@ void setup() { FRIEND_SENDING_INVITE_MEMBER_START_ID.intValue(), MAX_COUNT); for (Member member : sendingInviteToOwnerMembers) { entityManager.persist(member); + sendingInvitesFriendIds.add(member.getId()); FriendInvite sendingInvite = FriendInviteFixture.friendInvite(member, owner); entityManager.persist(sendingInvite); @@ -96,7 +108,7 @@ void setup() { @Test void 대량의_친구_초대를_저장하면_조회하면_친구_초대를_볼_수_있다() { //given - List friendIds = friends.stream() + List friendIds = bulkFriends.stream() .map(Member::getId) .toList(); @@ -254,16 +266,25 @@ private List getFriendInvites(EntityManager entityManager, Long ow @Test void 친구가_사용자에게_요청을_보낸_경우_사용자_아이디와_친구_아이디_목록으로_모든_요청_방향의_친구_초대를_조회하면_존재하는_단방향_친구_초대가_나온다() { //given - List friendIds = friends.stream() - .map(Member::getId) - .toList(); + //when + List friendInviteMemberIdsDtos = friendInviteQueryRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId( + sendingInvitesFriendIds, + ownerId + ); + + assertThat(friendInviteMemberIdsDtos).hasSize(sendingInvitesFriendIds.size()); + } + //Owner -> Friend + @Test + void 사용자가_친구에게_요청을_보낸_경우_사용자_아이디와_친구_아이디_목록으로_모든_요청_방향의_친구_초대를_조회하면_존재하는_단방향_친구_초대가_나온다() { + //given //when List friendInviteMemberIdsDtos = friendInviteQueryRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId( - friendIds, - owner.getId() + receivingInviteFriendIds, + ownerId ); - assertThat(friendInviteMemberIdsDtos).hasSize(friendIds.size()); + assertThat(friendInviteMemberIdsDtos).hasSize(receivingInviteFriendIds.size()); } } \ No newline at end of file From 7d3476f4d93432006331e7483b22b017839afc11 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Wed, 5 Jun 2024 01:15:50 +0900 Subject: [PATCH 112/195] =?UTF-8?q?test=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=A0=84=20=EC=B9=9C=EA=B5=AC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/fixture/dto/FriendDtoFixture.java | 21 ++++++ .../service/FriendQueryServiceTest.java | 73 ++++++++++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendDtoFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendDtoFixture.java index 5cf59d72a..4ee3f18b8 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendDtoFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/FriendDtoFixture.java @@ -1,9 +1,16 @@ package site.timecapsulearchive.core.common.fixture.dto; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; +import java.util.stream.IntStream; import java.util.stream.LongStream; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @@ -35,4 +42,18 @@ public static Optional getFriendSummaryDtoByTag() { .build()); } + + public static Slice getFriendSummaryDtoSlice(int count, boolean hasNextPage) { + List dtos = IntStream.range(0, count) + .mapToObj(i -> new FriendSummaryDto( + (long) i, + i + "testProfileUrl", + i + "testNickname", + ZonedDateTime.now(ZoneId.of("UTC")).plusDays(i) + ) + ) + .toList(); + + return new SliceImpl<>(dtos, Pageable.ofSize(count), hasNextPage); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java index aad366c83..2efac09e8 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java @@ -9,15 +9,21 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.FriendDtoFixture; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto; import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag; +import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest; import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; import site.timecapsulearchive.core.domain.friend.service.query.FriendQueryService; @@ -33,7 +39,10 @@ class FriendQueryServiceTest { private final GroupInviteRepository groupInviteRepository = mock(GroupInviteRepository.class); private final FriendQueryService friendQueryService = new FriendQueryService( - memberFriendRepository, memberGroupRepository, groupInviteRepository); + memberFriendRepository, + memberGroupRepository, + groupInviteRepository + ); @Test void 사용자는_주소록_기반_핸드폰_번호로_Ahchive_사용자_리스트를_조회_할_수_있다() { @@ -105,4 +114,66 @@ class FriendQueryServiceTest { assertThatThrownBy(() -> friendQueryService.searchFriend(memberId, tag)) .isInstanceOf(FriendNotFoundException.class); } + + @Test + void 그룹장은_그룹_초대_전_초대_가능한_친구_목록을_조회할_수_있다() { + //given + Long memberId = 1L; + Long groupId = 1L; + int size = 20; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(1); + + FriendBeforeGroupInviteRequest request = FriendBeforeGroupInviteRequest.of(memberId, + groupId, + size, now); + given(memberFriendRepository.findFriends(request)).willReturn( + FriendDtoFixture.getFriendSummaryDtoSlice(5, true)); + given(memberGroupRepository.getGroupMemberIdsByGroupId(request.groupId())).willReturn( + List.of(3L)); + given(groupInviteRepository.getGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), + request.memberId())).willReturn(List.of(4L)); + + Slice friendsBeforeGroupInviteSlice = friendQueryService.findFriendsBeforeGroupInviteSlice( + request); + + SoftAssertions.assertSoftly( + softly -> { + assertThat(friendsBeforeGroupInviteSlice.getContent()).isNotEmpty(); + assertThat(friendsBeforeGroupInviteSlice.getContent()).allMatch( + dto -> !dto.profileUrl().isBlank()); + assertThat(friendsBeforeGroupInviteSlice.getContent()).allMatch( + dto -> !dto.nickname().isBlank()); + assertThat(friendsBeforeGroupInviteSlice.getContent()).allMatch( + dto -> Objects.nonNull(dto.id())); + assertThat(friendsBeforeGroupInviteSlice.getContent()).allMatch( + dto -> Objects.nonNull(dto.createdAt())); + assertThat(friendsBeforeGroupInviteSlice.hasNext()).isTrue(); + assertThat(friendsBeforeGroupInviteSlice.getSize()).isEqualTo(5); + } + ); + } + + @Test + void 그룹장은_그룹_초대_전_이미_그룹멤버_혹은_그룹_요청을_보낸_사용자를_제외하고_초대_가능한_사용자를_조회한다() { + //given + Long memberId = 1L; + Long groupId = 1L; + int size = 20; + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")).plusDays(1); + + FriendBeforeGroupInviteRequest request = FriendBeforeGroupInviteRequest.of(memberId, + groupId, + size, now); + given(memberFriendRepository.findFriends(request)).willReturn( + FriendDtoFixture.getFriendSummaryDtoSlice(5, true)); + given(memberGroupRepository.getGroupMemberIdsByGroupId(request.groupId())).willReturn( + List.of(3L)); + given(groupInviteRepository.getGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), + request.memberId())).willReturn(List.of(4L)); + + Slice friendsBeforeGroupInviteSlice = friendQueryService.findFriendsBeforeGroupInviteSlice( + request); + + assertThat(friendsBeforeGroupInviteSlice.getContent()).isNotIn(3L, 4L); + } } \ No newline at end of file From e7ff2bae8021b03e63f9aa0189ba7fb81bb33e0d Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Wed, 5 Jun 2024 01:25:39 +0900 Subject: [PATCH 113/195] =?UTF-8?q?chore=20:=20=EC=9E=90=EB=B0=94=20?= =?UTF-8?q?=EA=B5=AC=EA=B8=80=20=ED=98=95=EC=8B=9D=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/friend/api/query/FriendQueryApi.java | 8 ++++---- .../friend_invite/FriendInviteQueryRepositoryImpl.java | 2 -- .../member_friend/MemberFriendQueryRepositoryImpl.java | 1 - .../core/domain/group/api/query/GroupQueryApi.java | 1 - .../domain/group/api/query/GroupQueryApiController.java | 6 ++---- .../domain/group/repository/GroupQueryRepository.java | 1 - .../core/domain/member/repository/MemberRepository.java | 1 - .../data/response/GroupSendingInviteMemberResponse.java | 4 +++- .../MemberGroupQueryRepository.java | 1 + .../member_group_repository/MemberGroupRepository.java | 1 - .../core/global/config/redis/RedissonLockAspect.java | 3 ++- 11 files changed, 12 insertions(+), 17 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java index 7fe063959..345a98c22 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/query/FriendQueryApi.java @@ -66,10 +66,10 @@ ResponseEntity> findFriendsBeforeGroupInvite( @Operation( summary = "소셜 친구 요청 받은 목록 조회", description = """ - 사용자가 소셜 친구 요청을 받은 목록을 보여준다. -
- 수락 대기 중인 요청만 해당한다. - """, + 사용자가 소셜 친구 요청을 받은 목록을 보여준다. +
+ 수락 대기 중인 요청만 해당한다. + """, security = {@SecurityRequirement(name = "user_token")}, tags = {"friend"} ) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java index ae6aeceef..947f78446 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/friend_invite/FriendInviteQueryRepositoryImpl.java @@ -12,9 +12,7 @@ import java.time.ZonedDateTime; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java index 598e5856d..f4d70e495 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/repository/member_friend/MemberFriendQueryRepositoryImpl.java @@ -1,6 +1,5 @@ package site.timecapsulearchive.core.domain.friend.repository.member_friend; -import static site.timecapsulearchive.core.domain.friend.entity.QFriendInvite.friendInvite; import static site.timecapsulearchive.core.domain.friend.entity.QMemberFriend.memberFriend; import static site.timecapsulearchive.core.domain.member.entity.QMember.member; 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 index 42695d39b..8cf12d1d5 100644 --- 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 @@ -9,7 +9,6 @@ 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.GroupMemberInfosResponse; import site.timecapsulearchive.core.domain.group.data.response.GroupsSliceResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; 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 index 4eb6a53ce..22349e302 100644 --- 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 @@ -1,7 +1,6 @@ package site.timecapsulearchive.core.domain.group.api.query; import java.time.ZonedDateTime; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.http.ResponseEntity; @@ -13,9 +12,7 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.group.data.dto.CompleteGroupSummaryDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailTotalDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.response.GroupDetailResponse; -import site.timecapsulearchive.core.domain.group.data.response.GroupMemberInfosResponse; 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; @@ -60,7 +57,8 @@ public ResponseEntity> findGroups( @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { - final Slice groupsSlice = groupQueryService.findGroupsSlice(memberId, + final Slice groupsSlice = groupQueryService.findGroupsSlice( + memberId, size, createdAt); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java index b13299a40..d9d6b63cf 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/repository/GroupQueryRepository.java @@ -5,7 +5,6 @@ import java.util.Optional; import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto; -import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.group.data.dto.GroupSummaryDto; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java index 8efa30cea..02c68c260 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java @@ -1,6 +1,5 @@ package site.timecapsulearchive.core.domain.member.repository; -import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java index b4193f30f..a01e1a4fa 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java @@ -11,9 +11,11 @@ public record GroupSendingInviteMemberResponse( String profileUrl, ZonedDateTime sendingInvitesCreatedAt ) { + public GroupSendingInviteMemberResponse { if (sendingInvitesCreatedAt != null) { - sendingInvitesCreatedAt = sendingInvitesCreatedAt.withZoneSameInstant(ResponseMappingConstant.ZONE_ID); + sendingInvitesCreatedAt = sendingInvitesCreatedAt.withZoneSameInstant( + ResponseMappingConstant.ZONE_ID); } } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java index 8d125b982..9d9c949e6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java @@ -18,5 +18,6 @@ public interface MemberGroupQueryRepository { List findGroupMemberInfos(Long memberId, Long groupId); Optional findGroupMembersCount(Long groupId); + List getGroupMemberIdsByGroupId(final Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupRepository.java index 39393cb7b..6f2a9cf28 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupRepository.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Optional; import org.springframework.data.repository.Repository; -import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; public interface MemberGroupRepository extends Repository, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java index f4444c523..5430e1499 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/redis/RedissonLockAspect.java @@ -32,7 +32,8 @@ public Object redissonLock(ProceedingJoinPoint joinPoint) throws Throwable { RedissonLock redissonLock = method.getAnnotation(RedissonLock.class); String lockKey = - method.getName() + DIVISION + RedisLockSpELParser.getLockKey(signature.getParameterNames(), + method.getName() + DIVISION + RedisLockSpELParser.getLockKey( + signature.getParameterNames(), joinPoint.getArgs(), redissonLock.value()); long waitTime = redissonLock.waitTime(); From b607f09fca51f07ba9f08a3c64c264a9bc36ffae Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Wed, 5 Jun 2024 10:17:21 +0900 Subject: [PATCH 114/195] =?UTF-8?q?fix=20:=20=EB=A9=A4=EB=B2=84=20profile?= =?UTF-8?q?=20=EC=84=A0=EC=96=B8=EB=90=9C=20URL=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/group/data/dto/GroupDetailTotalDto.java | 2 +- .../domain/group/data/dto/GroupMemberWithRelationDto.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java index 1770c5b8f..a9c60bf56 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupDetailTotalDto.java @@ -39,7 +39,7 @@ public static GroupDetailTotalDto as( public GroupDetailResponse toResponse(final Function singlePreSignUrlFunction) { List groupMemberResponses = members.stream() - .map(member -> member.toResponse(singlePreSignUrlFunction)) + .map(GroupMemberWithRelationDto::toResponse) .toList(); return GroupDetailResponse.builder() diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java index dbe86a084..0469152c2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/group/data/dto/GroupMemberWithRelationDto.java @@ -14,11 +14,10 @@ public record GroupMemberWithRelationDto( Boolean isFriend ) { - public GroupMemberWithRelationResponse toResponse( - final Function singlePreSignUrlFunction) { + public GroupMemberWithRelationResponse toResponse() { return GroupMemberWithRelationResponse.builder() .memberId(memberId) - .profileUrl(singlePreSignUrlFunction.apply(profileUrl)) + .profileUrl(profileUrl) .nickname(nickname) .tag(tag) .isOwner(isOwner) From db383c773e2170cab062ddc57f54947ecbfb49b7 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 18:17:30 +0900 Subject: [PATCH 115/195] =?UTF-8?q?fix=20:=20=EC=96=91=EB=B0=A9=ED=96=A5?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - equals & hash 코드를 제거하고 스트림 predicate로 검증 - transaction template dto return하도록 변경 --- .../data/dto/FriendInviteMemberIdsDto.java | 20 ----------- .../data/dto/FriendInviteNotificationDto.java | 18 ++++++++++ .../service/command/FriendCommandService.java | 33 ++++++++++++------- 3 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteNotificationDto.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java index 65f6132f1..02882eafb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteMemberIdsDto.java @@ -1,28 +1,8 @@ package site.timecapsulearchive.core.domain.friend.data.dto; -import java.util.Objects; - public record FriendInviteMemberIdsDto( Long ownerId, Long friendId ) { - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - FriendInviteMemberIdsDto that = (FriendInviteMemberIdsDto) o; - return (Objects.equals(ownerId, that.ownerId) && Objects.equals(friendId, that.friendId)) - || (Objects.equals(ownerId, that.friendId) && Objects.equals(friendId, that.ownerId)); - } - - @Override - public int hashCode() { - return Objects.hash(ownerId, friendId); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteNotificationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteNotificationDto.java new file mode 100644 index 000000000..55e84fb58 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/dto/FriendInviteNotificationDto.java @@ -0,0 +1,18 @@ +package site.timecapsulearchive.core.domain.friend.data.dto; + +import java.util.List; + +public record FriendInviteNotificationDto( + String nickname, + String profileUrl, + List foundFriendIds +) { + + public static FriendInviteNotificationDto create( + final String nickname, + final String profileUrl, + final List foundFriendIds + ) { + return new FriendInviteNotificationDto(nickname, profileUrl, foundFriendIds); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java index c02a3c5e9..ffd05fbf0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandService.java @@ -2,11 +2,13 @@ import java.util.List; import java.util.Optional; +import java.util.function.Predicate; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteMemberIdsDto; +import site.timecapsulearchive.core.domain.friend.data.dto.FriendInviteNotificationDto; import site.timecapsulearchive.core.domain.friend.entity.FriendInvite; import site.timecapsulearchive.core.domain.friend.entity.MemberFriend; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteDuplicateException; @@ -37,21 +39,21 @@ public void requestFriends(Long memberId, List friendIds) { return; } - final Member[] owner = new Member[1]; - final List[] foundFriendIds = new List[1]; - - transactionTemplate.executeWithoutResult(status -> { - owner[0] = memberRepository.findMemberById(memberId) + final FriendInviteNotificationDto dto = transactionTemplate.execute(status -> { + Member owner = memberRepository.findMemberById(memberId) .orElseThrow(MemberNotFoundException::new); - foundFriendIds[0] = memberRepository.findMemberIdsByIds(friendIds); + List foundFriendIds = memberRepository.findMemberIdsByIds(friendIds); + + friendInviteRepository.bulkSave(owner.getId(), foundFriendIds); - friendInviteRepository.bulkSave(owner[0].getId(), foundFriendIds[0]); + return FriendInviteNotificationDto.create(owner.getNickname(), owner.getProfileUrl(), + foundFriendIds); }); socialNotificationManager.sendFriendRequestMessages( - owner[0].getNickname(), - owner[0].getProfileUrl(), - foundFriendIds[0] + dto.nickname(), + dto.profileUrl(), + dto.foundFriendIds() ); } @@ -61,10 +63,19 @@ private List filterTwoWayAndDuplicateInvite(Long memberId, List frie return friendIds.stream() .filter(id -> !memberId.equals(id)) - .filter(id -> !twoWayIds.contains(new FriendInviteMemberIdsDto(id, memberId))) + .filter(isNotTwoWayInvite(memberId, twoWayIds)) .toList(); } + private Predicate isNotTwoWayInvite(Long memberId, + List twoWayIds) { + return id -> twoWayIds.stream() + .noneMatch(twoWayId -> + (twoWayId.ownerId().equals(id) && twoWayId.friendId().equals(memberId)) || + (twoWayId.ownerId().equals(memberId) && twoWayId.friendId().equals(id)) + ); + } + public void requestFriend(final Long memberId, final Long friendId) { validateSelfFriendOperation(memberId, friendId); validateTwoWayAndDuplicateInvite(memberId, friendId); From 67ab632b712c4c86748edef90d51ab5c39cc9337 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 18:17:40 +0900 Subject: [PATCH 116/195] =?UTF-8?q?fix=20:=20persist=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/friend/entity/FriendInvite.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java index a31f7b4ae..e1247de04 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/entity/FriendInvite.java @@ -27,11 +27,11 @@ public class FriendInvite extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "owner_id", nullable = false) private Member owner; - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "friend_id", nullable = false) private Member friend; From 859698982c6538da96f78068f06e865caf01cb6e Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 18:17:48 +0900 Subject: [PATCH 117/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/FriendCommandServiceTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java index e1ff42ee8..63523ef95 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/command/FriendCommandServiceTest.java @@ -7,6 +7,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.Collections; @@ -15,6 +16,7 @@ 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.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.FriendInviteMemberIdsDtoFixture; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteDuplicateException; import site.timecapsulearchive.core.domain.friend.exception.FriendInviteNotFoundException; @@ -93,6 +95,25 @@ class FriendCommandServiceTest { verify(transactionTemplate, never()).execute(any()); } + @Test + void 친구_요청을_보낸_경우_트랜잭션이_동작된다() { + //given + Long memberId = 1L; + List friendIds = List.of(2L, 3L, 4L, 5L, 6L); + List subFriendIds = friendIds.subList(0, 3); + given(friendInviteRepository.findFriendInviteMemberIdsDtoByMemberIdsAndFriendId(anyList(), + any())) + .willReturn(FriendInviteMemberIdsDtoFixture.twoWays(memberId, subFriendIds)); + given(memberRepository.findMemberById(anyLong())).willReturn( + Optional.ofNullable(MemberFixture.memberWithMemberId(0L))); + + //when + friendCommandService.requestFriends(memberId, friendIds); + + //then + verify(transactionTemplate, times(1)).execute(any()); + } + @Test void 사용자_본인한테_단건_친구_요청을_보낸_경우_예외가_발생한다() { //given From e3cc43d8217533acc28101013003d0486c743cb1 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 18:39:16 +0900 Subject: [PATCH 118/195] =?UTF-8?q?fix=20:=20s3=20presign=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/infra/queue/manager/SocialNotificationManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java index 888748a02..dbaf4e480 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java @@ -10,12 +10,14 @@ import site.timecapsulearchive.core.infra.queue.data.dto.FriendsReqNotificationsDto; import site.timecapsulearchive.core.infra.queue.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.GroupInviteNotificationDto; +import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; @Component @RequiredArgsConstructor public class SocialNotificationManager { private final RabbitTemplate basicRabbitTemplate; + private final S3PreSignedUrlManager s3PreSignedUrlManager; /** * 단건의 친구 요청을 받아서 알림 전송을 요청한다 @@ -59,10 +61,12 @@ public void sendFriendRequestMessages( return; } + String preSignedUrl = s3PreSignedUrlManager.getS3PreSignedUrlForGet(profileUrl); + basicRabbitTemplate.convertAndSend( RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_EXCHANGE.getSuccessComponent(), RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_QUEUE.getSuccessComponent(), - FriendsReqNotificationsDto.createOf(ownerNickname, profileUrl, targetIds) + FriendsReqNotificationsDto.createOf(ownerNickname, preSignedUrl, targetIds) ); } From 068392628b199cdb44cafec423805acef7aa9fc7 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 18:55:22 +0900 Subject: [PATCH 119/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EB=AA=A9=EB=A1=9D=20=EC=8A=AC=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 그룹 목록 초대 목록을 무한 스크롤 대비해 슬라이스로 변경 - 테스트 추가 --- .../api/query/MemberGroupQueryApi.java | 12 +++++-- .../query/MemberGroupQueryApiController.java | 21 ++++++++---- .../GroupSendingInvitesSliceRequestDto.java | 18 ++++++++++ ... => GroupSendingInvitesSliceResponse.java} | 14 +++++--- .../GroupInviteQueryRepository.java | 6 ++-- .../GroupInviteQueryRepositoryImpl.java | 31 ++++++++++++----- .../service/MemberGroupQueryService.java | 9 +++-- .../GroupInviteQueryRepositoryTest.java | 34 +++++++++++++------ 8 files changed, 105 insertions(+), 40 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesSliceRequestDto.java rename backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/{GroupSendingInvitesResponse.java => GroupSendingInvitesSliceResponse.java} (62%) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java index 9d4efaebc..f65919c37 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApi.java @@ -10,7 +10,7 @@ import org.springframework.http.ResponseEntity; import site.timecapsulearchive.core.domain.group.data.response.GroupMemberInfosResponse; import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceivingInvitesSliceResponse; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesSliceResponse; import site.timecapsulearchive.core.global.common.response.ApiSpec; public interface MemberGroupQueryApi { @@ -69,10 +69,16 @@ ResponseEntity> findGroupMemberInfos( description = "ok" ) }) - ResponseEntity> findGroupSendingInvites( + ResponseEntity> findGroupSendingInvites( Long memberId, @Parameter(in = ParameterIn.PATH, description = "그룹 아이디", required = true) - Long groupId + Long groupId, + + @Parameter(in = ParameterIn.QUERY, description = "마지막 그룹 초대 아이디") + Long groupInviteId, + + @Parameter(in = ParameterIn.QUERY, description = "페이지 크기", required = true) + int size ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java index ba7eb7371..b036638c7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/query/MemberGroupQueryApiController.java @@ -15,8 +15,9 @@ import site.timecapsulearchive.core.domain.group.data.response.GroupMemberInfosResponse; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesSliceRequestDto; import site.timecapsulearchive.core.domain.member_group.data.response.GroupReceivingInvitesSliceResponse; -import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesResponse; +import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInvitesSliceResponse; import site.timecapsulearchive.core.domain.member_group.service.MemberGroupQueryService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -84,17 +85,25 @@ public ResponseEntity> findGroupMemberInfos( produces = {"application/json"} ) @Override - public ResponseEntity> findGroupSendingInvites( + public ResponseEntity> findGroupSendingInvites( @AuthenticationPrincipal final Long memberId, - @PathVariable(value = "group_id") final Long groupId + @PathVariable(value = "group_id") final Long groupId, + @RequestParam(value = "group_invite_id", required = false) final Long groupInviteId, + @RequestParam(value = "size") final int size ) { - List groupSendingInvites = memberGroupQueryService.findGroupSendingInvites( - memberId, groupId); + GroupSendingInvitesSliceRequestDto dto = GroupSendingInvitesSliceRequestDto.create( + memberId, groupId, groupInviteId, size); + + Slice groupSendingInvitesSlice = memberGroupQueryService.findGroupSendingInvites( + dto); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - GroupSendingInvitesResponse.createOf(groupSendingInvites) + GroupSendingInvitesSliceResponse.createOf( + groupSendingInvitesSlice.getContent(), + groupSendingInvitesSlice.hasNext() + ) ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesSliceRequestDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesSliceRequestDto.java new file mode 100644 index 000000000..823e958b5 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInvitesSliceRequestDto.java @@ -0,0 +1,18 @@ +package site.timecapsulearchive.core.domain.member_group.data.dto; + +public record GroupSendingInvitesSliceRequestDto( + Long memberId, + Long groupId, + Long groupInviteId, + int size +) { + + public static GroupSendingInvitesSliceRequestDto create( + final Long memberId, + final Long groupId, + final Long groupInviteId, + final int size + ) { + return new GroupSendingInvitesSliceRequestDto(memberId, groupId, groupInviteId, size); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java similarity index 62% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesResponse.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java index 4be590970..24b231bbe 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInvitesSliceResponse.java @@ -5,19 +5,23 @@ import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; @Schema(description = "그룹 초대 보낸 목록") -public record GroupSendingInvitesResponse( +public record GroupSendingInvitesSliceResponse( @Schema(description = "초대 보낸 그룹원 정보 리스트") - List responses + List groupSendingInviteMembers, + + @Schema(description = "다음 페이지 유무") + boolean hasNext ) { - public static GroupSendingInvitesResponse createOf( - final List groupSendingInviteMemberDtos + public static GroupSendingInvitesSliceResponse createOf( + final List groupSendingInviteMemberDtos, + final boolean hasNext ) { List groupSendingInviteMemberResponses = groupSendingInviteMemberDtos.stream() .map(GroupSendingInviteMemberDto::toResponse) .toList(); - return new GroupSendingInvitesResponse(groupSendingInviteMemberResponses); + return new GroupSendingInvitesSliceResponse(groupSendingInviteMemberResponses, hasNext); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java index c9c5c3166..64e878abc 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Slice; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesSliceRequestDto; public interface GroupInviteQueryRepository { @@ -18,8 +19,7 @@ Slice findGroupReceivingInvitesSlice( final ZonedDateTime createdAt ); - List findGroupSendingInvites( - final Long memberId, - final Long groupId + Slice findGroupSendingInvites( + final GroupSendingInvitesSliceRequestDto dto ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index 7f2b33ec6..831b4e8e3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -6,6 +6,7 @@ import static site.timecapsulearchive.core.domain.member_group.entity.QGroupInvite.groupInvite; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -14,14 +15,13 @@ import java.time.ZonedDateTime; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesSliceRequestDto; import site.timecapsulearchive.core.global.util.SliceUtil; @Repository @@ -101,11 +101,10 @@ public Slice findGroupReceivingInvitesSlice( return SliceUtil.makeSlice(size, groupInviteSummaryDtos); } - public List findGroupSendingInvites( - final Long memberId, - final Long groupId + public Slice findGroupSendingInvites( + final GroupSendingInvitesSliceRequestDto dto ) { - return jpaQueryFactory + List groupSendingInviteMemberDtos = jpaQueryFactory .select( Projections.constructor( GroupSendingInviteMemberDto.class, @@ -117,8 +116,24 @@ public List findGroupSendingInvites( ) .from(groupInvite) .join(groupInvite.groupMember, member) - .where(groupInvite.group.id.eq(groupId) - .and(groupInvite.groupOwner.id.eq(memberId))) + .where( + groupInviteIdPagingCursorCondition(dto), + groupInvite.group.id.eq(dto.groupId()) + .and(groupInvite.groupOwner.id.eq(dto.memberId())) + ) + .orderBy(groupInvite.id.desc()) + .limit(dto.size() + 1) .fetch(); + + return SliceUtil.makeSlice(dto.size(), groupSendingInviteMemberDtos); + } + + private BooleanExpression groupInviteIdPagingCursorCondition( + GroupSendingInvitesSliceRequestDto dto) { + if (dto.groupInviteId() == null) { + return null; + } + + return groupInvite.id.lt(dto.groupInviteId()); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java index dd8141997..bd9f1c113 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupQueryService.java @@ -10,6 +10,7 @@ import site.timecapsulearchive.core.domain.group.exception.GroupNotFoundException; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesSliceRequestDto; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; @@ -41,10 +42,8 @@ public List findGroupMemberInfos( return memberGroupRepository.findGroupMemberInfos(memberId, groupId); } - public List findGroupSendingInvites( - final Long memberId, - final Long groupId - ) { - return groupInviteRepository.findGroupSendingInvites(memberId, groupId); + public Slice findGroupSendingInvites(final + GroupSendingInvitesSliceRequestDto dto) { + return groupInviteRepository.findGroupSendingInvites(dto); } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java index 5e8522e18..03c45d9fc 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -23,6 +23,7 @@ import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupInviteSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInviteMemberDto; +import site.timecapsulearchive.core.domain.member_group.data.dto.GroupSendingInvitesSliceRequestDto; import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteQueryRepository; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteQueryRepositoryImpl; @@ -37,6 +38,7 @@ class GroupInviteQueryRepositoryTest extends RepositoryTest { private Long groupId; private Long groupOwnerId; private Long groupMemberId; + private Long firstGroupInviteStartId; GroupInviteQueryRepositoryTest( JdbcTemplate jdbcTemplate, @@ -69,6 +71,10 @@ void setUp(@Autowired EntityManager entityManager) { GroupInvite groupInvite = GroupInvite.createOf(groups.get(i), groupOwners.get(i), groupMember); entityManager.persist(groupInvite); + + if (i == 0) { + firstGroupInviteStartId = groupInvite.getId(); + } } } @@ -129,19 +135,22 @@ void setUp(@Autowired EntityManager entityManager) { @Test void 그룹장은_자신이_보낸_그룹_초대_목록을_조회하면_그룹_초대목록이_나온다() { //given + GroupSendingInvitesSliceRequestDto requestDto = new GroupSendingInvitesSliceRequestDto( + groupOwnerId, groupId, null, 20 + ); + //when - List groupSendingInvites = groupInviteRepository.findGroupSendingInvites( - groupOwnerId, groupId); + Slice groupSendingInvites = groupInviteRepository.findGroupSendingInvites(requestDto); //then SoftAssertions.assertSoftly(softly -> { - softly.assertThat(groupSendingInvites).isNotEmpty(); - softly.assertThat(groupSendingInvites).allMatch(dto -> dto.id() != null); - softly.assertThat(groupSendingInvites) + softly.assertThat(groupSendingInvites.hasContent()).isTrue(); + softly.assertThat(groupSendingInvites.getContent()).allMatch(dto -> dto.id() != null); + softly.assertThat(groupSendingInvites.getContent()) .allMatch(dto -> dto.nickname() != null && !dto.nickname().isBlank()); - softly.assertThat(groupSendingInvites) + softly.assertThat(groupSendingInvites.getContent()) .allMatch(dto -> dto.profileUrl() != null && !dto.profileUrl().isBlank()); - softly.assertThat(groupSendingInvites) + softly.assertThat(groupSendingInvites.getContent()) .allMatch(dto -> dto.sendingInvitesCreatedAt() != null); }); } @@ -149,11 +158,16 @@ void setUp(@Autowired EntityManager entityManager) { @Test void 그룹원은_자신이_보낸_그룹_초대_목록을_조회하면_빈_리스트가_나온다() { //given + GroupSendingInvitesSliceRequestDto requestDto = new GroupSendingInvitesSliceRequestDto( + groupOwnerId, groupId, firstGroupInviteStartId - 1, 20 + ); + //when - List groupSendingInvites = groupInviteRepository.findGroupSendingInvites( - groupMemberId, groupId); + Slice groupSendingInvites = groupInviteRepository.findGroupSendingInvites( + requestDto + ); //then - assertThat(groupSendingInvites).isEmpty(); + assertThat(groupSendingInvites.isEmpty()).isTrue(); } } From 22c18ea582bbb931a2c05c2215a9740a3b6eea10 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 21:05:20 +0900 Subject: [PATCH 120/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=95=84=EC=9D=B4=EB=94=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 커서 기반 슬라이싱을 위한 groupInviteId 추가 --- .../member_group/data/dto/GroupSendingInviteMemberDto.java | 6 ++++-- .../data/response/GroupSendingInviteMemberResponse.java | 3 ++- .../GroupInviteQueryRepositoryImpl.java | 1 + .../repository/GroupInviteQueryRepositoryTest.java | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java index 2fcb0f82b..4b69a3178 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupSendingInviteMemberDto.java @@ -5,7 +5,8 @@ import site.timecapsulearchive.core.domain.member_group.data.response.GroupSendingInviteMemberResponse; public record GroupSendingInviteMemberDto( - Long id, + Long groupInviteId, + Long memberId, String nickname, String profileUrl, ZonedDateTime sendingInvitesCreatedAt @@ -13,7 +14,8 @@ public record GroupSendingInviteMemberDto( public GroupSendingInviteMemberResponse toResponse() { return GroupSendingInviteMemberResponse.builder() - .id(id) + .groupInviteId(groupInviteId) + .memberId(memberId) .nickname(nickname) .profileUrl(profileUrl) .sendingInvitesCreatedAt(sendingInvitesCreatedAt) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java index b4193f30f..cee057b8a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupSendingInviteMemberResponse.java @@ -6,7 +6,8 @@ @Builder public record GroupSendingInviteMemberResponse( - Long id, + Long groupInviteId, + Long memberId, String nickname, String profileUrl, ZonedDateTime sendingInvitesCreatedAt diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index 831b4e8e3..b3ffe71ce 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -108,6 +108,7 @@ public Slice findGroupSendingInvites( .select( Projections.constructor( GroupSendingInviteMemberDto.class, + groupInvite.id, member.id, member.nickname, member.profileUrl, diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java index 03c45d9fc..d9d51aeae 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -145,7 +145,8 @@ void setUp(@Autowired EntityManager entityManager) { //then SoftAssertions.assertSoftly(softly -> { softly.assertThat(groupSendingInvites.hasContent()).isTrue(); - softly.assertThat(groupSendingInvites.getContent()).allMatch(dto -> dto.id() != null); + softly.assertThat(groupSendingInvites.getContent()).allMatch(dto -> dto.groupInviteId() != null); + softly.assertThat(groupSendingInvites.getContent()).allMatch(dto -> dto.memberId() != null); softly.assertThat(groupSendingInvites.getContent()) .allMatch(dto -> dto.nickname() != null && !dto.nickname().isBlank()); softly.assertThat(groupSendingInvites.getContent()) From c0a38806bad984486c916e55411d2c58bc501045 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 21:24:22 +0900 Subject: [PATCH 121/195] =?UTF-8?q?test=20:=20=EC=8A=AC=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupInviteQueryRepositoryTest.java | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java index d9d51aeae..753f7c901 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/repository/GroupInviteQueryRepositoryTest.java @@ -32,13 +32,14 @@ class GroupInviteQueryRepositoryTest extends RepositoryTest { private static final int MAX_GROUP_COUNT = 2; + private static final int MAX_GROUP_INVITE_COUNT = 10; private final GroupInviteQueryRepository groupInviteRepository; private Long groupId; private Long groupOwnerId; private Long groupMemberId; - private Long firstGroupInviteStartId; + private Long firstGroupInviteStartId = null; GroupInviteQueryRepositoryTest( JdbcTemplate jdbcTemplate, @@ -56,10 +57,12 @@ void setUp(@Autowired EntityManager entityManager) { groupOwners.forEach(entityManager::persist); groupOwnerId = groupOwners.get(0).getId(); - //그룹 초대 올 그룹원 - Member groupMember = MemberFixture.member(2); - entityManager.persist(groupMember); - groupMemberId = groupMember.getId(); + //그룹 초대 올 그룹원들 + List groupMembers = MemberFixture.members(2, MAX_GROUP_INVITE_COUNT); + for (Member groupMember : groupMembers) { + entityManager.persist(groupMember); + } + groupMemberId = groupMembers.get(0).getId(); // 그룹들 List groups = GroupFixture.groups(0, MAX_GROUP_COUNT); @@ -68,12 +71,14 @@ void setUp(@Autowired EntityManager entityManager) { // 그룹원에게 초대온 그룹 초대들 for (int i = 0; i < MAX_GROUP_COUNT; i++) { - GroupInvite groupInvite = GroupInvite.createOf(groups.get(i), groupOwners.get(i), - groupMember); - entityManager.persist(groupInvite); - - if (i == 0) { - firstGroupInviteStartId = groupInvite.getId(); + for (Member groupMember : groupMembers) { + GroupInvite groupInvite = GroupInvite.createOf(groups.get(i), groupOwners.get(i), + groupMember); + entityManager.persist(groupInvite); + + if (firstGroupInviteStartId == null) { + firstGroupInviteStartId = groupInvite.getId(); + } } } } @@ -140,13 +145,16 @@ void setUp(@Autowired EntityManager entityManager) { ); //when - Slice groupSendingInvites = groupInviteRepository.findGroupSendingInvites(requestDto); + Slice groupSendingInvites = groupInviteRepository.findGroupSendingInvites( + requestDto); //then SoftAssertions.assertSoftly(softly -> { softly.assertThat(groupSendingInvites.hasContent()).isTrue(); - softly.assertThat(groupSendingInvites.getContent()).allMatch(dto -> dto.groupInviteId() != null); - softly.assertThat(groupSendingInvites.getContent()).allMatch(dto -> dto.memberId() != null); + softly.assertThat(groupSendingInvites.getContent()) + .allMatch(dto -> dto.groupInviteId() != null); + softly.assertThat(groupSendingInvites.getContent()) + .allMatch(dto -> dto.memberId() != null); softly.assertThat(groupSendingInvites.getContent()) .allMatch(dto -> dto.nickname() != null && !dto.nickname().isBlank()); softly.assertThat(groupSendingInvites.getContent()) @@ -171,4 +179,24 @@ void setUp(@Autowired EntityManager entityManager) { //then assertThat(groupSendingInvites.isEmpty()).isTrue(); } + + @Test + void 사용자는_그룹_초대_보낸_목록_첫_페이지를_조회_후_다음_페이지에서_그룹_초대_보낸_목록을_조회_할_수_있다() { + //given + GroupSendingInvitesSliceRequestDto dto = GroupSendingInvitesSliceRequestDto.create( + groupOwnerId, groupId, null, MAX_GROUP_INVITE_COUNT / 2); + Slice firstSlice = groupInviteRepository.findGroupSendingInvites( + dto); + + //when + GroupSendingInviteMemberDto lastGroupInvite = firstSlice.getContent() + .get(firstSlice.getNumberOfElements() - 1); + Slice nextSlice = groupInviteRepository.findGroupSendingInvites( + GroupSendingInvitesSliceRequestDto.create(groupOwnerId, groupId, + lastGroupInvite.groupInviteId(), 20) + ); + + //then + assertThat(nextSlice.getContent()).isNotEmpty(); + } } From edf34ff31be9bbc4459ac5b788bcf4f1fd09a881 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 11:00:13 +0900 Subject: [PATCH 122/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=95=84=EC=9D=B4=EB=94=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member_group/data/dto/GroupInviteSummaryDto.java | 2 +- .../data/response/GroupReceivingInviteSummaryResponse.java | 3 +++ .../GroupInviteQueryRepositoryImpl.java | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java index a5fb7fb54..ab0a06cc3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/dto/GroupInviteSummaryDto.java @@ -7,7 +7,7 @@ @Builder public record GroupInviteSummaryDto( - + Long groupInviteId, Long groupId, String groupName, String groupProfileUrl, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInviteSummaryResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInviteSummaryResponse.java index 13d154fff..24d6cd9ec 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInviteSummaryResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/data/response/GroupReceivingInviteSummaryResponse.java @@ -9,6 +9,9 @@ @Schema(description = "초대온 그룹 요약 정보") public record GroupReceivingInviteSummaryResponse( + @Schema(description = "그룹 초대 아이디") + Long groupInviteId, + @Schema(description = "그룹 아이디") Long groupId, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index b3ffe71ce..2f1ff36aa 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -83,6 +83,7 @@ public Slice findGroupReceivingInvitesSlice( .select( Projections.constructor( GroupInviteSummaryDto.class, + groupInvite.id, group.id, group.groupName, group.groupProfileUrl, From 68cc6727fc798d29a93bb1adb71392c15c1da8c5 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Wed, 5 Jun 2024 21:45:24 +0900 Subject: [PATCH 123/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=A1=B0=ED=9A=8C,=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group_invite_repository/GroupInviteRepository.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java index b882c940d..f8cb010ad 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @@ -12,6 +13,8 @@ public interface GroupInviteRepository extends Repository, void save(GroupInvite groupInvite); + void delete(GroupInvite groupInvite); + @Query("delete from GroupInvite gi " + "where gi.group.id =:groupId " + "and gi.groupOwner.id =:groupOwnerId " @@ -27,5 +30,7 @@ int deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( @Query("delete from GroupInvite gi where gi.id in :groupInviteIds") @Modifying void bulkDelete(@Param("groupInviteIds") List groupInviteIds); + + Optional findGroupInviteById(Long groupInviteId); } From 7d7e941955be00ba6ee8f26ae2568db8b19241f6 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 11:08:48 +0900 Subject: [PATCH 124/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=82=AD=EC=A0=9C=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/member_group/entity/GroupInvite.java | 6 ++++++ .../service/MemberGroupCommandService.java | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java index dc67cce81..cc06587e7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java @@ -14,6 +14,7 @@ import lombok.NoArgsConstructor; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; import site.timecapsulearchive.core.global.entity.BaseEntity; @Entity @@ -49,4 +50,9 @@ public static GroupInvite createOf(Group group, Member groupOwner, Member groupM return new GroupInvite(group, groupOwner, groupMember); } + public void validateOwner(Long memberId) { + if (!groupOwner.equals(memberId)) { + throw new NoGroupAuthorityException(); + } + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index 35dc7b94c..bb7ba23e9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -14,6 +14,7 @@ import site.timecapsulearchive.core.domain.member_group.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; +import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; @@ -105,6 +106,16 @@ private void deleteGroupInvite(final Long memberId, final Long groupId, } } + @Transactional + public void deleteGroupInvite(final Long memberId, final Long groupInviteId) { + final GroupInvite groupInvite = groupInviteRepository.findGroupInviteById(groupInviteId) + .orElseThrow(GroupInviteNotFoundException::new); + + groupInvite.validateOwner(memberId); + + groupInviteRepository.delete(groupInvite); + } + /** * 사용자는 사용자가 속한 그룹을 탈퇴한다. *
주의 - 그룹 탈퇴 시 아래 조건에 해당하면 예외가 발생한다. From 11f50f7111d37fa309bb1a912d0b70fd7e135af9 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 11:27:07 +0900 Subject: [PATCH 125/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=82=AD=EC=A0=9C=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/command/MemberGroupCommandApi.java | 29 +++++++++++++++++++ .../MemberGroupCommandApiController.java | 14 +++++++++ 2 files changed, 43 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java index 9b9d85e67..b00c098c3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java @@ -107,6 +107,35 @@ ResponseEntity> acceptGroupInvitation( Long groupId ); + @Operation( + summary = "보낸 그룹 초대 삭제 ", + description = "그룹장이 보낸 그룹 초대를 삭제한다. 그룹장만 그룹 초대를 삭제할 수 있다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"member group"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리 완료" + ), + @ApiResponse( + responseCode = "403", + description = "그룹장이 아닌 경우 예외가 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "그룹 초대를 찾을 수 없는 경우 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + }) + ResponseEntity> deleteGroupInvite( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "보낸 그룹 초대 아이디", required = true) + Long groupInviteId + ); + @Operation( summary = "그룹 요청 거부", description = "특정 그룹으로부터 초대 요청을 거부한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java index 1c6434a44..7577c6635 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApiController.java @@ -64,6 +64,20 @@ public ResponseEntity> acceptGroupInvitation( ); } + @DeleteMapping(value = "/sending-invites/{group_invite_id}") + public ResponseEntity> deleteGroupInvite( + @AuthenticationPrincipal final Long memberId, + @PathVariable("group_invite_id") final Long groupInviteId + ) { + memberGroupCommandService.deleteGroupInvite(memberId, groupInviteId); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); + } + @DeleteMapping(value = "/{group_id}/member/{target_id}/reject") public ResponseEntity> rejectGroupInvitation( @AuthenticationPrincipal final Long memberId, From 1def8c2f0f167b52e812f5bfb331de5fc31f666b Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 11:58:06 +0900 Subject: [PATCH 126/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fixture/domain/GroupInviteFixture.java | 14 +++++ .../MemberGroupCommandServiceTest.java | 55 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupInviteFixture.java diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupInviteFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupInviteFixture.java new file mode 100644 index 000000000..7b31b2300 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupInviteFixture.java @@ -0,0 +1,14 @@ +package site.timecapsulearchive.core.common.fixture.domain; + +import java.util.Optional; +import site.timecapsulearchive.core.domain.group.entity.Group; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; + +public class GroupInviteFixture { + + public static Optional groupInvite(Group group, Member groupOwner, + Member groupMember) { + return Optional.of(GroupInvite.createOf(group, groupOwner, groupMember)); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 955a6eaf4..8e5b1ae15 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -19,6 +19,7 @@ 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.GroupInviteFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberGroupFixture; import site.timecapsulearchive.core.common.fixture.dto.GroupDtoFixture; @@ -30,6 +31,7 @@ import site.timecapsulearchive.core.domain.member_group.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.domain.member_group.data.dto.GroupOwnerSummaryDto; import site.timecapsulearchive.core.domain.member_group.data.request.SendGroupRequest; +import site.timecapsulearchive.core.domain.member_group.entity.GroupInvite; import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup; import site.timecapsulearchive.core.domain.member_group.exception.GroupInviteNotFoundException; import site.timecapsulearchive.core.domain.member_group.exception.GroupMemberCountLimitException; @@ -303,6 +305,59 @@ class MemberGroupCommandServiceTest { verify(memberGroupRepository, times(1)).delete(any(MemberGroup.class)); } + @Test + void 그룹장인_사람이_그룹_초대를_삭제하면_삭제된다() { + //given + Long groupOwnerId = 1L; + Long groupMemberId = 2L; + Long groupInviteId = 1L; + given(groupInviteRepository.findGroupInviteById(anyLong())).willReturn( + GroupInviteFixture.groupInvite(GroupFixture.group(), + MemberFixture.memberWithMemberId(groupOwnerId), + MemberFixture.memberWithMemberId(groupMemberId)) + ); + + //when + groupMemberCommandService.deleteGroupInvite(groupOwnerId, groupInviteId); + + //then + verify(groupInviteRepository, times(1)).delete(any(GroupInvite.class)); + } + + @Test + void 그룹_초대가_존재하지_않는_경우_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupInviteId = 1L; + given(groupInviteRepository.findGroupInviteById(anyLong())).willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> groupMemberCommandService.deleteGroupInvite(groupOwnerId, groupInviteId)) + .isInstanceOf(GroupInviteNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); + } + + @Test + void 그룹장이_아닌_사람이_그룹_초대를_삭제하면_예외가_발생한다() { + //given + Long groupOwnerId = 1L; + Long groupMemberId = 2L; + Long groupInviteId = 1L; + given(groupInviteRepository.findGroupInviteById(anyLong())).willReturn( + GroupInviteFixture.groupInvite(GroupFixture.group(), + MemberFixture.memberWithMemberId(groupOwnerId), + MemberFixture.memberWithMemberId(groupMemberId)) + ); + + //when + //then + assertThatThrownBy( + () -> groupMemberCommandService.deleteGroupInvite(groupMemberId, groupInviteId)) + .isInstanceOf(NoGroupAuthorityException.class) + .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); + } + @Test void 나_자신을_그룹에서_삭제하려하면_예외가_발생한다() { //given From b19ab6e272ed16c4a8d71229393c0b453c594cb1 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 12:03:06 +0900 Subject: [PATCH 127/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EB=B9=84=EA=B5=90=20=EC=98=A4=EB=A5=98=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 --- .../core/domain/member_group/entity/GroupInvite.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java index cc06587e7..0b6623295 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java @@ -51,7 +51,7 @@ public static GroupInvite createOf(Group group, Member groupOwner, Member groupM } public void validateOwner(Long memberId) { - if (!groupOwner.equals(memberId)) { + if (!groupOwner.getId().equals(memberId)) { throw new NoGroupAuthorityException(); } } From 276cc8f067503f93f7dd8b40da597b3501ef0b9c Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 12:11:34 +0900 Subject: [PATCH 128/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존의 조회해서 권한을 확인하는 대신 조회 시 그룹장 아이디 추가 --- .../api/command/MemberGroupCommandApi.java | 5 --- .../member_group/entity/GroupInvite.java | 6 ---- .../GroupInviteRepository.java | 2 +- .../service/MemberGroupCommandService.java | 6 ++-- .../MemberGroupCommandServiceTest.java | 33 +++++-------------- 5 files changed, 12 insertions(+), 40 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java index b00c098c3..39b8e8daa 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/api/command/MemberGroupCommandApi.java @@ -118,11 +118,6 @@ ResponseEntity> acceptGroupInvitation( responseCode = "200", description = "처리 완료" ), - @ApiResponse( - responseCode = "403", - description = "그룹장이 아닌 경우 예외가 발생한다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), @ApiResponse( responseCode = "404", description = "그룹 초대를 찾을 수 없는 경우 발생한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java index 0b6623295..95473cef4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/entity/GroupInvite.java @@ -49,10 +49,4 @@ private GroupInvite(Group group, Member groupOwner, Member groupMember) { public static GroupInvite createOf(Group group, Member groupOwner, Member groupMember) { return new GroupInvite(group, groupOwner, groupMember); } - - public void validateOwner(Long memberId) { - if (!groupOwner.getId().equals(memberId)) { - throw new NoGroupAuthorityException(); - } - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java index f8cb010ad..64cddd3a8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteRepository.java @@ -31,6 +31,6 @@ int deleteGroupInviteByGroupIdAndGroupOwnerIdAndGroupMemberId( @Modifying void bulkDelete(@Param("groupInviteIds") List groupInviteIds); - Optional findGroupInviteById(Long groupInviteId); + Optional findGroupInviteByIdAndGroupOwnerId(Long groupInviteId, Long groupOwnerId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java index bb7ba23e9..a1fedde35 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandService.java @@ -108,11 +108,11 @@ private void deleteGroupInvite(final Long memberId, final Long groupId, @Transactional public void deleteGroupInvite(final Long memberId, final Long groupInviteId) { - final GroupInvite groupInvite = groupInviteRepository.findGroupInviteById(groupInviteId) + final GroupInvite groupInvite = groupInviteRepository.findGroupInviteByIdAndGroupOwnerId( + groupInviteId, + memberId) .orElseThrow(GroupInviteNotFoundException::new); - groupInvite.validateOwner(memberId); - groupInviteRepository.delete(groupInvite); } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java index 8e5b1ae15..9aa2299a3 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member_group/service/MemberGroupCommandServiceTest.java @@ -311,11 +311,12 @@ class MemberGroupCommandServiceTest { Long groupOwnerId = 1L; Long groupMemberId = 2L; Long groupInviteId = 1L; - given(groupInviteRepository.findGroupInviteById(anyLong())).willReturn( - GroupInviteFixture.groupInvite(GroupFixture.group(), + given(groupInviteRepository.findGroupInviteByIdAndGroupOwnerId(anyLong(), + anyLong())) + .willReturn(GroupInviteFixture.groupInvite(GroupFixture.group(), MemberFixture.memberWithMemberId(groupOwnerId), MemberFixture.memberWithMemberId(groupMemberId)) - ); + ); //when groupMemberCommandService.deleteGroupInvite(groupOwnerId, groupInviteId); @@ -329,35 +330,17 @@ class MemberGroupCommandServiceTest { //given Long groupOwnerId = 1L; Long groupInviteId = 1L; - given(groupInviteRepository.findGroupInviteById(anyLong())).willReturn(Optional.empty()); + given(groupInviteRepository.findGroupInviteByIdAndGroupOwnerId(anyLong(), + anyLong())).willReturn(Optional.empty()); //when //then - assertThatThrownBy(() -> groupMemberCommandService.deleteGroupInvite(groupOwnerId, groupInviteId)) + assertThatThrownBy( + () -> groupMemberCommandService.deleteGroupInvite(groupOwnerId, groupInviteId)) .isInstanceOf(GroupInviteNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_INVITATION_NOT_FOUND_ERROR.getMessage()); } - @Test - void 그룹장이_아닌_사람이_그룹_초대를_삭제하면_예외가_발생한다() { - //given - Long groupOwnerId = 1L; - Long groupMemberId = 2L; - Long groupInviteId = 1L; - given(groupInviteRepository.findGroupInviteById(anyLong())).willReturn( - GroupInviteFixture.groupInvite(GroupFixture.group(), - MemberFixture.memberWithMemberId(groupOwnerId), - MemberFixture.memberWithMemberId(groupMemberId)) - ); - - //when - //then - assertThatThrownBy( - () -> groupMemberCommandService.deleteGroupInvite(groupMemberId, groupInviteId)) - .isInstanceOf(NoGroupAuthorityException.class) - .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); - } - @Test void 나_자신을_그룹에서_삭제하려하면_예외가_발생한다() { //given From e475fb00befe870700be82dd7bf5514a70c777ea Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 6 Jun 2024 13:25:40 +0900 Subject: [PATCH 129/195] =?UTF-8?q?fix=20:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=9D=BC=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/friend/service/query/FriendQueryService.java | 4 ++-- .../GroupInviteQueryRepository.java | 2 +- .../GroupInviteQueryRepositoryImpl.java | 2 +- .../MemberGroupQueryRepository.java | 2 +- .../MemberGroupQueryRepositoryImpl.java | 2 +- .../domain/friend/service/FriendQueryServiceTest.java | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index 47dfd562d..d7da2b7be 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -43,8 +43,8 @@ public Slice findFriendsBeforeGroupInviteSlice( request); final List groupMemberIdsToExcludeBeforeGroupInvite = Stream.concat( - memberGroupRepository.getGroupMemberIdsByGroupId(request.groupId()).stream(), - groupInviteRepository.getGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), + memberGroupRepository.findGroupMemberIdsByGroupId(request.groupId()).stream(), + groupInviteRepository.findGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), request.memberId()).stream()) .distinct() .toList(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java index 60c47491c..65a8aa3f2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepository.java @@ -18,7 +18,7 @@ Slice findGroupReceivingInvitesSlice( final ZonedDateTime createdAt ); - List getGroupMemberIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); + List findGroupMemberIdsByGroupIdAndGroupOwnerId(final Long groupId, final Long memberId); List findGroupSendingInvites( final Long memberId, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index a81dec5b7..abd1b5b4e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -103,7 +103,7 @@ public Slice findGroupReceivingInvitesSlice( } @Override - public List getGroupMemberIdsByGroupIdAndGroupOwnerId( + public List findGroupMemberIdsByGroupIdAndGroupOwnerId( final Long groupId, final Long groupOwnerId ) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java index 9d9c949e6..b11a3eade 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java @@ -19,5 +19,5 @@ public interface MemberGroupQueryRepository { Optional findGroupMembersCount(Long groupId); - List getGroupMemberIdsByGroupId(final Long groupId); + List findGroupMemberIdsByGroupId(final Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java index ab4c26fcc..84eb64bfe 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java @@ -103,7 +103,7 @@ public Optional findGroupMembersCount(final Long groupId) { ); } - public List getGroupMemberIdsByGroupId(final Long groupId) { + public List findGroupMemberIdsByGroupId(final Long groupId) { return jpaQueryFactory .select(memberGroup.member.id) .from(memberGroup) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java index ae52bdf62..0abefe6bb 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/FriendQueryServiceTest.java @@ -132,9 +132,9 @@ class FriendQueryServiceTest { size, now); given(memberFriendRepository.findFriends(request)).willReturn( FriendDtoFixture.getFriendSummaryDtoSlice(5, true)); - given(memberGroupRepository.getGroupMemberIdsByGroupId(request.groupId())).willReturn( + given(memberGroupRepository.findGroupMemberIdsByGroupId(request.groupId())).willReturn( List.of(3L)); - given(groupInviteRepository.getGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), + given(groupInviteRepository.findGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), request.memberId())).willReturn(List.of(4L)); Slice friendsBeforeGroupInviteSlice = friendQueryService.findFriendsBeforeGroupInviteSlice( @@ -170,9 +170,9 @@ class FriendQueryServiceTest { size, now); given(memberFriendRepository.findFriends(request)).willReturn( FriendDtoFixture.getFriendSummaryDtoSlice(5, true)); - given(memberGroupRepository.getGroupMemberIdsByGroupId(request.groupId())).willReturn( + given(memberGroupRepository.findGroupMemberIdsByGroupId(request.groupId())).willReturn( List.of(3L)); - given(groupInviteRepository.getGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), + given(groupInviteRepository.findGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), request.memberId())).willReturn(List.of(4L)); Slice friendsBeforeGroupInviteSlice = friendQueryService.findFriendsBeforeGroupInviteSlice( From 005134003c178d0d49de53010ed93aeead035f8f Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Thu, 6 Jun 2024 13:30:47 +0900 Subject: [PATCH 130/195] =?UTF-8?q?fix=20:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/query/FriendQueryService.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index d7da2b7be..15e711f91 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -42,19 +42,33 @@ public Slice findFriendsBeforeGroupInviteSlice( final Slice friendSummaryDtos = memberFriendRepository.findFriends( request); - final List groupMemberIdsToExcludeBeforeGroupInvite = Stream.concat( + final List groupMemberIdsToExcludeBeforeGroupInvite = getGroupMemberIdsToExcludeBeforeGroupInvite( + request); + + final List friendSummaryBeforeGroupInvitedDtos = getFriendSummaryBeforeGroupInvitedDtos( + friendSummaryDtos, groupMemberIdsToExcludeBeforeGroupInvite); + + return new SliceImpl<>(friendSummaryBeforeGroupInvitedDtos, friendSummaryDtos.getPageable(), + friendSummaryDtos.hasNext()); + } + + private List getGroupMemberIdsToExcludeBeforeGroupInvite( + final FriendBeforeGroupInviteRequest request) { + return Stream.concat( memberGroupRepository.findGroupMemberIdsByGroupId(request.groupId()).stream(), groupInviteRepository.findGroupMemberIdsByGroupIdAndGroupOwnerId(request.groupId(), request.memberId()).stream()) .distinct() .toList(); + } - final List friendSummaryBeforeGroupInvitedDtos = friendSummaryDtos.getContent() + private List getFriendSummaryBeforeGroupInvitedDtos( + final Slice friendSummaryDtos, + final List groupMemberIdsToExcludeBeforeGroupInvite + ) { + return friendSummaryDtos.getContent() .stream() .filter(dto -> !groupMemberIdsToExcludeBeforeGroupInvite.contains(dto.id())).toList(); - - return new SliceImpl<>(friendSummaryBeforeGroupInvitedDtos, friendSummaryDtos.getPageable(), - friendSummaryDtos.hasNext()); } public Slice findFriendReceivingInvitesSlice( From 6bfdb4e787f73200f87b25b5d3797d3144142b06 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 14:26:17 +0900 Subject: [PATCH 131/195] =?UTF-8?q?feat=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EC=A1=B0=ED=9A=8C,=20=EA=B7=B8=EB=A3=B9?= =?UTF-8?q?=20=EC=BA=A1=EC=8A=90=20=EA=B0=9C=EB=B4=89=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CapsuleRepository.java | 9 +++++++++ .../GroupCapsuleOpenQueryRepository.java | 16 +++------------- .../repository/GroupCapsuleOpenRepository.java | 3 +++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java index 03908bba4..f50bfd7ef 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/CapsuleRepository.java @@ -23,4 +23,13 @@ int updateIsOpenedTrue( @Param("memberId") Long memberId, @Param("capsuleId") Long capsuleId ); + + @Query("select c " + + "from Capsule c" + + " where c.id = :capsuleId and c.member.id = :memberId " + + "and c.type = 'GROUP' and c.group.id is not null") + Optional findGroupCapsuleByMemberIdAndCapsuleId( + @Param("memberId") Long memberId, + @Param("capsuleId") Long capsuleId + ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java index d6e6d03f2..218d84b9b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java @@ -1,8 +1,5 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.repository; -import static site.timecapsulearchive.core.domain.capsule.entity.QGroupCapsuleOpen.groupCapsuleOpen; - -import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; @@ -20,7 +17,6 @@ public class GroupCapsuleOpenQueryRepository { private final JdbcTemplate jdbcTemplate; - private final JPAQueryFactory jpaQueryFactory; public void bulkSave(final List groupMemberIds, final Capsule capsule) { jdbcTemplate.batchUpdate( @@ -32,8 +28,10 @@ INSERT INTO group_capsule_open ( new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { + boolean isOpened = capsule.getDueDate() == null; + ps.setNull(1, Types.BIGINT); - ps.setBoolean(2, false); + ps.setBoolean(2, isOpened); ps.setLong(3, groupMemberIds.get(i)); ps.setLong(4, capsule.getId()); ps.setTimestamp(5, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); @@ -47,12 +45,4 @@ public int getBatchSize() { } ); } - - public List findIsOpenedByCapsuleId(final Long capsuleId) { - return jpaQueryFactory - .select(groupCapsuleOpen.isOpened) - .from(groupCapsuleOpen) - .where(groupCapsuleOpen.capsule.id.eq(capsuleId)) - .fetch(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java index ce02c9eee..4450d78bb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenRepository.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.repository.Repository; import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; @@ -9,4 +10,6 @@ public interface GroupCapsuleOpenRepository extends Repository findByMemberIdAndCapsuleId(Long memberId, Long capsuleId); + + List findByCapsuleId(Long capsuleId); } From 29ccb7d88d8fb792679a5d65fcef2a485a2e9027 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 14:44:13 +0900 Subject: [PATCH 132/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EA=B0=9C=EB=B4=89=20=EB=A1=9C=EC=A7=81=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 --- .../core/domain/capsule/entity/Capsule.java | 4 +++ .../capsule/entity/GroupCapsuleOpen.java | 4 +++ .../service/GroupCapsuleService.java | 31 +++++++++++++------ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java index feab13c7b..e220714b1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java @@ -111,4 +111,8 @@ public boolean isNotCapsuleOpened() { return dueDate.isAfter(ZonedDateTime.now()); } + + public void open() { + this.isOpened = Boolean.TRUE; + } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java index d1e94f550..809d9dc64 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java @@ -53,4 +53,8 @@ public static GroupCapsuleOpen createOf(Member member, Capsule capsule, Boolean public void open() { this.isOpened = Boolean.TRUE; } + + public boolean matched(Long capsuleId, Long memberId) { + return capsule.getId().equals(capsuleId) && member.getId().equals(memberId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java index d40189ad3..4ed4d368f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java @@ -33,7 +33,6 @@ public class GroupCapsuleService { private final CapsuleRepository capsuleRepository; private final GroupCapsuleQueryRepository groupCapsuleQueryRepository; private final GroupCapsuleOpenRepository groupCapsuleOpenRepository; - private final GroupCapsuleOpenQueryRepository groupCapsuleOpenQueryRepository; @Transactional public Capsule saveGroupCapsule( @@ -104,19 +103,31 @@ public Slice findMyGroupCapsuleSlice( */ @Transactional public void openGroupCapsule(final Long memberId, final Long capsuleId) { - GroupCapsuleOpen groupCapsuleOpen = groupCapsuleOpenRepository.findByMemberIdAndCapsuleId( - memberId, - capsuleId) - .orElseThrow(GroupCapsuleOpenNotFoundException::new); - groupCapsuleOpen.open(); + Capsule groupCapsule = capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(memberId, capsuleId) + .orElseThrow(CapsuleNotFondException::new); + + if (groupCapsule.getDueDate() == null) { + groupCapsule.open(); + return; + } - List capsuleOpens = groupCapsuleOpenQueryRepository.findIsOpenedByCapsuleId( + List groupCapsuleOpens = groupCapsuleOpenRepository.findByCapsuleId( capsuleId); + if (groupCapsuleOpens.isEmpty()) { + throw new GroupCapsuleOpenNotFoundException(); + } + + boolean allGroupMemberOpened = groupCapsuleOpens.stream() + .allMatch(groupCapsuleOpen -> { + if (groupCapsuleOpen.matched(capsuleId, memberId)) { + groupCapsuleOpen.open(); + } + + return groupCapsuleOpen.getIsOpened(); + }); - boolean allGroupMemberOpened = capsuleOpens.stream() - .allMatch(isOpened -> isOpened.equals(Boolean.TRUE)); if (allGroupMemberOpened) { - capsuleRepository.updateIsOpenedTrue(memberId, capsuleId); + groupCapsule.open(); } } } From 06bf21ac74ac11951b9897876f74cf5c0015c3e5 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 14:44:21 +0900 Subject: [PATCH 133/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=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/CapsuleFixture.java | 24 ++++ .../domain/GroupCapsuleOpenFixture.java | 18 +++ .../GroupCapsuleOpenQueryRepositoryTest.java | 4 +- .../service/GroupCapsuleServiceTest.java | 112 ++++++++++++++---- 4 files changed, 133 insertions(+), 25 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java index 386ed7a1e..1c07fff4e 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.common.fixture.domain; +import java.lang.reflect.Field; import java.time.ZonedDateTime; import java.util.List; import java.util.stream.IntStream; @@ -91,4 +92,27 @@ public static Capsule groupCapsule(Member member, CapsuleSkin capsuleSkin, Group .group(group) .build(); } + + public static Capsule groupCapsuleWithCapsuleId( + Member member, + CapsuleSkin capsuleSkin, + Group group, + Long capsuleId + ) { + try { + Capsule capsule = groupCapsule(member, capsuleSkin, group); + + setFieldValue(capsule, "id", capsuleId); + return capsule; + } 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); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java index 795913fd7..7b0c0c6bc 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java @@ -1,7 +1,10 @@ package site.timecapsulearchive.core.common.fixture.domain; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; @@ -33,4 +36,19 @@ public static Optional groupCapsuleOpen(int dataPrefix) { ) ); } + + public static List groupCapsuleOpensNotAllOpened( + Capsule capsule, + List groupMembers + ) { + int mid = groupMembers.size() / 2; + List opened = groupCapsuleOpens(true, capsule, + groupMembers.subList(0, mid)); + List notOpened = groupCapsuleOpens(false, capsule, + groupMembers.subList(mid, groupMembers.size() - 1)); + + return Stream.of(opened, notOpened) + .flatMap(Collection::stream) + .toList(); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java index e7f245f2d..4a9c6c10d 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java @@ -37,13 +37,11 @@ class GroupCapsuleOpenQueryRepositoryTest extends RepositoryTest { private List groupCapsuleOpens; public GroupCapsuleOpenQueryRepositoryTest( - JPAQueryFactory jpaQueryFactory, JdbcTemplate jdbcTemplate, DataSource dataSource ) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate, - jpaQueryFactory); + this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate); } @Transactional diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java index c6caa072a..20a6ffb87 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java @@ -1,28 +1,34 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.service; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.anyLong; 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 static org.mockito.Mockito.verifyNoInteractions; import java.time.ZonedDateTime; +import java.util.Collections; import java.util.List; import java.util.Optional; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; +import site.timecapsulearchive.core.common.fixture.domain.CapsuleFixture; +import site.timecapsulearchive.core.common.fixture.domain.CapsuleSkinFixture; import site.timecapsulearchive.core.common.fixture.domain.GroupCapsuleOpenFixture; +import site.timecapsulearchive.core.common.fixture.domain.GroupFixture; +import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.CapsuleDtoFixture; +import site.timecapsulearchive.core.domain.capsule.entity.Capsule; +import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; +import site.timecapsulearchive.core.domain.capsule.exception.CapsuleNotFondException; import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; import site.timecapsulearchive.core.domain.capsule.generic_capsule.data.dto.CapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; -import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberSummaryDto; +import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.error.ErrorCode; class GroupCapsuleServiceTest { @@ -35,13 +41,9 @@ class GroupCapsuleServiceTest { GroupCapsuleQueryRepository.class); private final GroupCapsuleOpenRepository groupCapsuleOpenRepository = mock( GroupCapsuleOpenRepository.class); - private final GroupCapsuleOpenQueryRepository groupCapsuleOpenQueryRepository = mock( - GroupCapsuleOpenQueryRepository.class); private final GroupCapsuleService groupCapsuleService = new GroupCapsuleService( - capsuleRepository, groupCapsuleQueryRepository, groupCapsuleOpenRepository, - groupCapsuleOpenQueryRepository - ); + capsuleRepository, groupCapsuleQueryRepository, groupCapsuleOpenRepository); @Test void 개봉된_그룹_캡슐의_상세_내용을_볼_수_있다() { @@ -201,14 +203,14 @@ class GroupCapsuleServiceTest { //given Long memberId = 1L; Long capsuleId = 1L; - given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) + given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(Optional.empty()); //when //then assertThatThrownBy(() -> groupCapsuleService.openGroupCapsule(memberId, capsuleId)) - .isInstanceOf(GroupCapsuleOpenNotFoundException.class) - .hasMessageContaining(ErrorCode.GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR.getMessage()); + .isInstanceOf(CapsuleNotFondException.class) + .hasMessageContaining(ErrorCode.CAPSULE_NOT_FOUND_ERROR.getMessage()); } @Test @@ -216,17 +218,35 @@ class GroupCapsuleServiceTest { //given Long memberId = 1L; Long capsuleId = 1L; - given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) - .willReturn(GroupCapsuleOpenFixture.groupCapsuleOpen(memberId.intValue())); - given(groupCapsuleOpenQueryRepository.findIsOpenedByCapsuleId(anyLong())) - .willReturn(List.of(Boolean.FALSE, Boolean.FALSE, Boolean.TRUE)); + Optional groupCapsule = getGroupCapsule(memberId, capsuleId); + given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsule); + + List groupMembers = MemberFixture.membersWithMemberId(memberId.intValue() + 2, 4); + given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)) + .willReturn( + GroupCapsuleOpenFixture.groupCapsuleOpens(false, groupCapsule.get(), groupMembers) + ); //when groupCapsuleService.openGroupCapsule(memberId, capsuleId); //then - verifyNoInteractions(capsuleRepository); + assertThat(groupCapsule.get().getIsOpened()).isFalse(); + } + + private Optional getGroupCapsule(Long memberId, Long capsuleId) { + Member member = MemberFixture.memberWithMemberId(memberId); + + return Optional.ofNullable( + CapsuleFixture.groupCapsuleWithCapsuleId( + member, + CapsuleSkinFixture.capsuleSkin(member), + GroupFixture.group(), + capsuleId + ) + ); } @Test @@ -234,15 +254,63 @@ class GroupCapsuleServiceTest { //given Long memberId = 1L; Long capsuleId = 1L; - given(groupCapsuleOpenRepository.findByMemberIdAndCapsuleId(anyLong(), anyLong())) - .willReturn(GroupCapsuleOpenFixture.groupCapsuleOpen(memberId.intValue())); - given(groupCapsuleOpenQueryRepository.findIsOpenedByCapsuleId(anyLong())) - .willReturn(List.of(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE)); + + Optional groupCapsule = getGroupCapsule(memberId, capsuleId); + given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsule); + + List groupMembers = MemberFixture.membersWithMemberId(memberId.intValue() + 2, 4); + given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)) + .willReturn( + GroupCapsuleOpenFixture.groupCapsuleOpens(true, groupCapsule.get(), groupMembers) + ); //when groupCapsuleService.openGroupCapsule(memberId, capsuleId); //then - verify(capsuleRepository, times(1)).updateIsOpenedTrue(anyLong(), anyLong()); + assertThat(groupCapsule.get().getIsOpened()).isTrue(); + } + + @Test + void 일부_그룹원이_캡슐을_개봉한_경우_캡슐은_개봉되지_않는다() { + //given + Long memberId = 1L; + Long capsuleId = 1L; + + Optional groupCapsule = getGroupCapsule(memberId, capsuleId); + given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsule); + + List groupMembers = MemberFixture.membersWithMemberId(memberId.intValue() + 2, 4); + given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)) + .willReturn( + GroupCapsuleOpenFixture.groupCapsuleOpensNotAllOpened(groupCapsule.get(), groupMembers) + ); + + //when + groupCapsuleService.openGroupCapsule(memberId, capsuleId); + + //then + assertThat(groupCapsule.get().getIsOpened()).isFalse(); + } + + @Test + void 모든_그룹원이_캡슐을_개봉이_없는_경우_예외가_발생한다() { + //given + Long memberId = 1L; + Long capsuleId = 1L; + + Optional groupCapsule = getGroupCapsule(memberId, capsuleId); + given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsule); + given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)).willReturn( + Collections.emptyList()); + + //when + //then + assertThatThrownBy(() -> groupCapsuleService.openGroupCapsule(memberId, capsuleId)) + .isInstanceOf(GroupCapsuleOpenNotFoundException.class) + .hasMessageContaining(ErrorCode.GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR.getMessage()); } } From d45ac975eb4716810e0a9450781c55157e61c873 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 14:46:36 +0900 Subject: [PATCH 134/195] =?UTF-8?q?fix=20:=20presign=20url=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/queue/manager/SocialNotificationManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java index dbaf4e480..8b251c60b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java @@ -61,12 +61,10 @@ public void sendFriendRequestMessages( return; } - String preSignedUrl = s3PreSignedUrlManager.getS3PreSignedUrlForGet(profileUrl); - basicRabbitTemplate.convertAndSend( RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_EXCHANGE.getSuccessComponent(), RabbitmqComponentConstants.FRIEND_REQUEST_NOTIFICATION_QUEUE.getSuccessComponent(), - FriendsReqNotificationsDto.createOf(ownerNickname, preSignedUrl, targetIds) + FriendsReqNotificationsDto.createOf(ownerNickname, profileUrl, targetIds) ); } @@ -75,10 +73,12 @@ public void sendGroupInviteMessage( final String groupProfileUrl, final List targetIds ) { + String preSignedUrl = s3PreSignedUrlManager.getS3PreSignedUrlForGet(groupProfileUrl); + basicRabbitTemplate.convertAndSend( RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_EXCHANGE.getSuccessComponent(), RabbitmqComponentConstants.GROUP_INVITE_NOTIFICATION_QUEUE.getSuccessComponent(), - GroupInviteNotificationDto.createOf(ownerNickname, groupProfileUrl, targetIds) + GroupInviteNotificationDto.createOf(ownerNickname, preSignedUrl, targetIds) ); } From 0127f95d04cf51da5b26cdea7b61fdee0ff512e9 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 19:37:49 +0900 Subject: [PATCH 135/195] =?UTF-8?q?fix=20:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 객체가 직접 판단하도록 함수 이동 --- .../core/domain/capsule/entity/Capsule.java | 4 ++++ .../capsule/group_capsule/service/GroupCapsuleService.java | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java index e220714b1..094d43ace 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java @@ -115,4 +115,8 @@ public boolean isNotCapsuleOpened() { public void open() { this.isOpened = Boolean.TRUE; } + + public boolean isTimeCapsule() { + return dueDate != null; + } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java index 4ed4d368f..d41693ff4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java @@ -18,7 +18,6 @@ import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleSummaryDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.MyGroupCapsuleDto; -import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; @@ -106,7 +105,7 @@ public void openGroupCapsule(final Long memberId, final Long capsuleId) { Capsule groupCapsule = capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(memberId, capsuleId) .orElseThrow(CapsuleNotFondException::new); - if (groupCapsule.getDueDate() == null) { + if (!groupCapsule.isTimeCapsule()) { groupCapsule.open(); return; } From 2c5355e1c457dea45490d88b8cd46214e5a88235 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 21:07:24 +0900 Subject: [PATCH 136/195] =?UTF-8?q?fix=20:=20=EA=B0=9C=EB=B4=89=EC=9D=BC?= =?UTF-8?q?=EC=9D=B4=20=EC=95=88=EB=90=9C=20=EC=BA=A1=EC=8A=90=20=EC=97=B4?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/capsule/entity/Capsule.java | 7 ++++- .../group_capsule/api/GroupCapsuleApi.java | 3 ++- .../api/GroupCapsuleApiController.java | 14 +++++++--- .../data/dto/CapsuleOpenStatus.java | 14 ++++++++++ .../data/dto/GroupCapsuleOpenStateDto.java | 23 ++++++++++++++++ .../GroupCapsuleOpenStateResponse.java | 10 +++++++ .../service/GroupCapsuleService.java | 27 +++++++++++++------ .../core/global/error/ErrorCode.java | 1 + 8 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/CapsuleOpenStatus.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleOpenStateDto.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupCapsuleOpenStateResponse.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java index 094d43ace..0325fd7ba 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java @@ -14,6 +14,7 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import lombok.AccessLevel; @@ -109,7 +110,7 @@ public boolean isNotCapsuleOpened() { return false; } - return dueDate.isAfter(ZonedDateTime.now()); + return dueDate.isAfter(ZonedDateTime.now(ZoneId.of("UTC"))); } public void open() { @@ -119,4 +120,8 @@ public void open() { public boolean isTimeCapsule() { return dueDate != null; } + + public boolean canOpen() { + return dueDate == null || dueDate.isBefore(ZonedDateTime.now(ZoneId.of("UTC"))); + } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java index 7a5371a4f..06fb9b364 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java @@ -22,6 +22,7 @@ import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleCreateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleUpdateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleDetailResponse; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleOpenStateResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsulePageResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleSummaryResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.MyGroupCapsuleSliceResponse; @@ -172,7 +173,7 @@ ResponseEntity> getMyGroupCapsules( content = @Content(schema = @Schema(implementation = ErrorResponse.class)) ) }) - ResponseEntity> openCapsule( + ResponseEntity> openCapsule( Long memberId, @Parameter(in = ParameterIn.PATH, description = "개봉할 그룹 캡슐 아이디", required = true) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java index 9b6089e3a..63f19c19a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java @@ -14,11 +14,13 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleOpenStateDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleSummaryDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.MyGroupCapsuleDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleCreateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleUpdateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleDetailResponse; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleOpenStateResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsulePageResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleSummaryResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.MyGroupCapsuleSliceResponse; @@ -139,13 +141,19 @@ public ResponseEntity> getMyGroupCapsules( @PostMapping("/{capsule_id}/open") @Override - public ResponseEntity> openCapsule( + public ResponseEntity> openCapsule( @AuthenticationPrincipal final Long memberId, @PathVariable(value = "capsule_id") final Long capsuleId ) { - groupCapsuleService.openGroupCapsule(memberId, capsuleId); + GroupCapsuleOpenStateDto groupCapsuleOpenStateDto = groupCapsuleService.openGroupCapsule( + memberId, capsuleId); - return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + groupCapsuleOpenStateDto.toResponse() + ) + ); } @Override diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/CapsuleOpenStatus.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/CapsuleOpenStatus.java new file mode 100644 index 000000000..750a8fb14 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/CapsuleOpenStatus.java @@ -0,0 +1,14 @@ +package site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto; + +import lombok.Getter; + +@Getter +public enum CapsuleOpenStatus { + OPEN("캡슐이 개봉되었습니다."), NOT_OPEN("캡슐이 개봉되지 않았습니다."); + + private final String statusMessage; + + CapsuleOpenStatus(String statusMessage) { + this.statusMessage = statusMessage; + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleOpenStateDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleOpenStateDto.java new file mode 100644 index 000000000..56fd18e2a --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupCapsuleOpenStateDto.java @@ -0,0 +1,23 @@ +package site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto; + +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleOpenStateResponse; + +public record GroupCapsuleOpenStateDto( + CapsuleOpenStatus capsuleOpenStatus +) { + + public static GroupCapsuleOpenStateDto opened() { + return new GroupCapsuleOpenStateDto(CapsuleOpenStatus.OPEN); + } + + public static GroupCapsuleOpenStateDto notOpened() { + return new GroupCapsuleOpenStateDto(CapsuleOpenStatus.NOT_OPEN); + } + + public GroupCapsuleOpenStateResponse toResponse() { + return new GroupCapsuleOpenStateResponse( + capsuleOpenStatus, + capsuleOpenStatus.getStatusMessage() + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupCapsuleOpenStateResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupCapsuleOpenStateResponse.java new file mode 100644 index 000000000..e77ec5c3a --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupCapsuleOpenStateResponse.java @@ -0,0 +1,10 @@ +package site.timecapsulearchive.core.domain.capsule.group_capsule.data.response; + +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.CapsuleOpenStatus; + +public record GroupCapsuleOpenStateResponse( + CapsuleOpenStatus capsuleOpenStatus, + String statusMessage +) { + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java index d41693ff4..7ed593728 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java @@ -18,6 +18,7 @@ import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleSummaryDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.MyGroupCapsuleDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleOpenStateDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; @@ -101,22 +102,36 @@ public Slice findMyGroupCapsuleSlice( * @param capsuleId 개봉할 캡슐 아이디 */ @Transactional - public void openGroupCapsule(final Long memberId, final Long capsuleId) { - Capsule groupCapsule = capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(memberId, capsuleId) + public GroupCapsuleOpenStateDto openGroupCapsule(final Long memberId, final Long capsuleId) { + Capsule groupCapsule = capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(memberId, + capsuleId) .orElseThrow(CapsuleNotFondException::new); + if (!groupCapsule.canOpen()) { + return GroupCapsuleOpenStateDto.notOpened(); + } + if (!groupCapsule.isTimeCapsule()) { groupCapsule.open(); - return; + return GroupCapsuleOpenStateDto.opened(); + } + + boolean allGroupMemberOpened = isAllGroupMemberOpened(memberId, capsuleId); + if (allGroupMemberOpened) { + groupCapsule.open(); } + return GroupCapsuleOpenStateDto.opened(); + } + + private boolean isAllGroupMemberOpened(Long memberId, Long capsuleId) { List groupCapsuleOpens = groupCapsuleOpenRepository.findByCapsuleId( capsuleId); if (groupCapsuleOpens.isEmpty()) { throw new GroupCapsuleOpenNotFoundException(); } - boolean allGroupMemberOpened = groupCapsuleOpens.stream() + return groupCapsuleOpens.stream() .allMatch(groupCapsuleOpen -> { if (groupCapsuleOpen.matched(capsuleId, memberId)) { groupCapsuleOpen.open(); @@ -124,10 +139,6 @@ public void openGroupCapsule(final Long memberId, final Long capsuleId) { return groupCapsuleOpen.getIsOpened(); }); - - if (allGroupMemberOpened) { - groupCapsule.open(); - } } } 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 f29833617..f4ef1f2ca 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 @@ -57,6 +57,7 @@ public enum ErrorCode { CAPSULE_NOT_FOUND_ERROR(404, "CAPSULE-001", "캡슐을 찾지 못하였습니다."), NO_CAPSULE_AUTHORITY_ERROR(403, "CAPSULE-002", "캡슐에 접근 권한이 없습니다."), GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR(404, "CAPSULE-003", "그룹 캡슐 개봉상태를 찾을 수 없습니다."), + CAPSULE_OPEN_STATE_ERROR(400, "CAPSULE-004", "캡슐을 개봉할 수 없는 상태입니다."), //friend FRIEND_NOT_FOUND_ERROR(404, "FRIEND-001", "친구를 찾지 못하였습니다"), From 248e9d005cd37c6acd07a10371b29be2ca8a1ed4 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 21:07:32 +0900 Subject: [PATCH 137/195] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EB=B0=8F=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/CapsuleFixture.java | 27 ++++ .../service/GroupCapsuleServiceTest.java | 150 +++++++++++++----- 2 files changed, 133 insertions(+), 44 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java index 1c07fff4e..83f029ad3 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java @@ -115,4 +115,31 @@ private static void setFieldValue(Object instance, String fieldName, Object valu field.setAccessible(true); field.set(instance, value); } + + public static Capsule notGroupTimeCapsuleWithCapsuleId( + Member member, + CapsuleSkin capsuleSkin, + Group group, + Long capsuleId, + ZonedDateTime now + ) { + Capsule capsule = Capsule.builder() + .dueDate(now) + .title("testTitle") + .content("testContent") + .type(CapsuleType.GROUP) + .address(getTestAddress()) + .point(geoTransformManager.changePoint4326To3857(TEST_LATITUDE, TEST_LONGITUDE)) + .member(member) + .capsuleSkin(capsuleSkin) + .group(group) + .build(); + + try { + setFieldValue(capsule, "id", capsuleId); + return capsule; + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java index 20a6ffb87..bb8b5333f 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java @@ -1,16 +1,16 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.service; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; import java.util.Optional; -import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; import site.timecapsulearchive.core.common.fixture.domain.CapsuleFixture; import site.timecapsulearchive.core.common.fixture.domain.CapsuleSkinFixture; @@ -19,12 +19,13 @@ import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.CapsuleDtoFixture; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; -import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; import site.timecapsulearchive.core.domain.capsule.exception.CapsuleNotFondException; import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; import site.timecapsulearchive.core.domain.capsule.generic_capsule.data.dto.CapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleRepository; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.CapsuleOpenStatus; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleOpenStateDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberSummaryDto; @@ -34,6 +35,7 @@ class GroupCapsuleServiceTest { private final Long capsuleId = 1L; + private final Long memberId = 1L; private final int groupMemberCount = 3; private final CapsuleRepository capsuleRepository = mock(CapsuleRepository.class); @@ -58,7 +60,7 @@ class GroupCapsuleServiceTest { capsuleId); //then - SoftAssertions.assertSoftly(softly -> { + assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); List members = response.members(); softly.assertThat(response).isNotNull(); @@ -84,7 +86,7 @@ class GroupCapsuleServiceTest { capsuleId); //then - SoftAssertions.assertSoftly(softly -> { + assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); List members = response.members(); softly.assertThat(response).isNotNull(); @@ -110,7 +112,7 @@ class GroupCapsuleServiceTest { capsuleId); //then - SoftAssertions.assertSoftly(softly -> { + assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); List members = response.members(); softly.assertThat(response).isNotNull(); @@ -137,7 +139,7 @@ class GroupCapsuleServiceTest { capsuleId); //then - SoftAssertions.assertSoftly(softly -> { + assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); List members = response.members(); softly.assertThat(response).isNotNull(); @@ -162,7 +164,7 @@ class GroupCapsuleServiceTest { capsuleId); //then - SoftAssertions.assertSoftly(softly -> { + assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); List members = response.members(); softly.assertThat(response).isNotNull(); @@ -187,7 +189,7 @@ class GroupCapsuleServiceTest { capsuleId); //then - SoftAssertions.assertSoftly(softly -> { + assertSoftly(softly -> { CapsuleDetailDto detailDto = response.capsuleDetailDto(); List members = response.members(); softly.assertThat(response).isNotNull(); @@ -199,10 +201,8 @@ class GroupCapsuleServiceTest { } @Test - void 그룹_캡슐_개봉이_없는_캡슐을_개봉하려는_경우_예외가_발생한다() { + void 그룹_캡슐이_없는_경우_그룹_캡슐_개봉_시_예외가_발생한다() { //given - Long memberId = 1L; - Long capsuleId = 1L; given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(Optional.empty()); @@ -214,47 +214,66 @@ class GroupCapsuleServiceTest { } @Test - void 모든_그룹원이_캡슐을_개봉하지_않은_경우_캡슐은_개봉되지_않는다() { + void 개봉일이_지나지_않아_그룹_캡슐을_열_수_없는_경우_캡슐은_개봉되지_않는다() { //given - Long memberId = 1L; - Long capsuleId = 1L; - - Optional groupCapsule = getGroupCapsule(memberId, capsuleId); + ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")); + Optional groupCapsule = getGroupCapsuleSpecificTime(memberId, capsuleId, + now.plusYears(5)); given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); - List groupMembers = MemberFixture.membersWithMemberId(memberId.intValue() + 2, 4); - given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)) - .willReturn( - GroupCapsuleOpenFixture.groupCapsuleOpens(false, groupCapsule.get(), groupMembers) - ); + //when + GroupCapsuleOpenStateDto groupCapsuleOpenStateDto = groupCapsuleService.openGroupCapsule( + memberId, capsuleId); + + //then + assertSoftly(softly -> { + softly.assertThat(groupCapsule.get().getIsOpened()).isFalse(); + softly.assertThat(groupCapsuleOpenStateDto.capsuleOpenStatus()) + .isEqualTo(CapsuleOpenStatus.NOT_OPEN); + }); + } + + @Test + void 타임캡슐이_아닌_경우_그룹_캡슐_개봉_시_그룹_캡슐은_개봉된다() { + //given + Optional groupCapsule = getGroupCapsuleSpecificTime(memberId, capsuleId, null); + given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsule); //when - groupCapsuleService.openGroupCapsule(memberId, capsuleId); + GroupCapsuleOpenStateDto groupCapsuleOpenStateDto = groupCapsuleService.openGroupCapsule( + memberId, capsuleId); //then - assertThat(groupCapsule.get().getIsOpened()).isFalse(); + assertSoftly(softly -> { + softly.assertThat(groupCapsule.get().getIsOpened()).isTrue(); + softly.assertThat(groupCapsuleOpenStateDto.capsuleOpenStatus()) + .isEqualTo(CapsuleOpenStatus.OPEN); + }); } - private Optional getGroupCapsule(Long memberId, Long capsuleId) { + private Optional getGroupCapsuleSpecificTime( + Long memberId, + Long capsuleId, + ZonedDateTime now + ) { Member member = MemberFixture.memberWithMemberId(memberId); return Optional.ofNullable( - CapsuleFixture.groupCapsuleWithCapsuleId( + CapsuleFixture.notGroupTimeCapsuleWithCapsuleId( member, CapsuleSkinFixture.capsuleSkin(member), GroupFixture.group(), - capsuleId + capsuleId, + now ) ); } @Test - void 모든_그룹원이_캡슐을_개봉한_경우_캡슐은_개봉된다() { + void 모든_그룹원이_캡슐을_개봉하지_않은_경우_캡슐은_개봉되지_않는다() { //given - Long memberId = 1L; - Long capsuleId = 1L; - Optional groupCapsule = getGroupCapsule(memberId, capsuleId); given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); @@ -262,22 +281,37 @@ private Optional getGroupCapsule(Long memberId, Long capsuleId) { List groupMembers = MemberFixture.membersWithMemberId(memberId.intValue() + 2, 4); given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)) .willReturn( - GroupCapsuleOpenFixture.groupCapsuleOpens(true, groupCapsule.get(), groupMembers) + GroupCapsuleOpenFixture.groupCapsuleOpens(false, groupCapsule.get(), groupMembers) ); //when - groupCapsuleService.openGroupCapsule(memberId, capsuleId); + GroupCapsuleOpenStateDto groupCapsuleOpenStateDto = groupCapsuleService.openGroupCapsule( + memberId, capsuleId); //then - assertThat(groupCapsule.get().getIsOpened()).isTrue(); + assertSoftly(softly -> { + softly.assertThat(groupCapsule.get().getIsOpened()).isFalse(); + softly.assertThat(groupCapsuleOpenStateDto.capsuleOpenStatus()).isEqualTo( + CapsuleOpenStatus.OPEN); + }); + } + + private Optional getGroupCapsule(Long memberId, Long capsuleId) { + Member member = MemberFixture.memberWithMemberId(memberId); + + return Optional.ofNullable( + CapsuleFixture.groupCapsuleWithCapsuleId( + member, + CapsuleSkinFixture.capsuleSkin(member), + GroupFixture.group(), + capsuleId + ) + ); } @Test void 일부_그룹원이_캡슐을_개봉한_경우_캡슐은_개봉되지_않는다() { //given - Long memberId = 1L; - Long capsuleId = 1L; - Optional groupCapsule = getGroupCapsule(memberId, capsuleId); given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); @@ -285,22 +319,25 @@ private Optional getGroupCapsule(Long memberId, Long capsuleId) { List groupMembers = MemberFixture.membersWithMemberId(memberId.intValue() + 2, 4); given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)) .willReturn( - GroupCapsuleOpenFixture.groupCapsuleOpensNotAllOpened(groupCapsule.get(), groupMembers) + GroupCapsuleOpenFixture.groupCapsuleOpensNotAllOpened(groupCapsule.get(), + groupMembers) ); //when - groupCapsuleService.openGroupCapsule(memberId, capsuleId); + GroupCapsuleOpenStateDto groupCapsuleOpenStateDto = groupCapsuleService.openGroupCapsule( + memberId, capsuleId); //then - assertThat(groupCapsule.get().getIsOpened()).isFalse(); + assertSoftly(softly -> { + softly.assertThat(groupCapsule.get().getIsOpened()).isFalse(); + softly.assertThat(groupCapsuleOpenStateDto.capsuleOpenStatus()).isEqualTo( + CapsuleOpenStatus.OPEN); + }); } @Test - void 모든_그룹원이_캡슐을_개봉이_없는_경우_예외가_발생한다() { + void 그룹_캡슐_개봉이_없는_경우_예외가_발생한다() { //given - Long memberId = 1L; - Long capsuleId = 1L; - Optional groupCapsule = getGroupCapsule(memberId, capsuleId); given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); @@ -313,4 +350,29 @@ private Optional getGroupCapsule(Long memberId, Long capsuleId) { .isInstanceOf(GroupCapsuleOpenNotFoundException.class) .hasMessageContaining(ErrorCode.GROUP_CAPSULE_OPEN_NOT_FOUND_ERROR.getMessage()); } + + @Test + void 모든_그룹원이_캡슐을_개봉한_경우_캡슐은_개봉된다() { + //given + Optional groupCapsule = getGroupCapsule(memberId, capsuleId); + given(capsuleRepository.findGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + .willReturn(groupCapsule); + + List groupMembers = MemberFixture.membersWithMemberId(memberId.intValue() + 2, 4); + given(groupCapsuleOpenRepository.findByCapsuleId(capsuleId)) + .willReturn( + GroupCapsuleOpenFixture.groupCapsuleOpens(true, groupCapsule.get(), groupMembers) + ); + + //when + GroupCapsuleOpenStateDto groupCapsuleOpenStateDto = groupCapsuleService.openGroupCapsule( + memberId, capsuleId); + + //then + assertSoftly(softly -> { + softly.assertThat(groupCapsule.get().getIsOpened()).isTrue(); + softly.assertThat(groupCapsuleOpenStateDto.capsuleOpenStatus()).isEqualTo( + CapsuleOpenStatus.OPEN); + }); + } } From 091815b96532a219b265074b0388fef15e9dec38 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 6 Jun 2024 22:29:13 +0900 Subject: [PATCH 138/195] =?UTF-8?q?fix=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EA=B0=9C=EB=B4=89=20=EB=A1=9C=EC=A7=81=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 - 객체지향적으로 변경 --- .../core/domain/capsule/entity/Capsule.java | 23 ++++++++++++-- .../repository/CapsuleRepository.java | 9 +++--- .../GroupCapsuleOpenRepository.java | 6 ---- .../service/GroupCapsuleService.java | 31 +++---------------- 4 files changed, 30 insertions(+), 39 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java index 0325fd7ba..ba19b34e7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java @@ -16,12 +16,14 @@ import jakarta.persistence.Table; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.locationtech.jts.geom.Point; +import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; @@ -61,13 +63,13 @@ public class Capsule extends BaseEntity { private Address address; @OneToMany(mappedBy = "capsule", cascade = CascadeType.ALL, orphanRemoval = true) - private List images; + private List images = new ArrayList<>(); @OneToMany(mappedBy = "capsule", cascade = CascadeType.ALL, orphanRemoval = true) - private List