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