Skip to content

Commit

Permalink
feat : 그룹 초대 기능 구현
Browse files Browse the repository at this point in the history
그룹장은 그룹원을 초대할 수 있다
  • Loading branch information
GaBaljaintheroom committed May 8, 2024
1 parent 42efafb commit 4d0dcda
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public ResponseEntity<ApiSpec<String>> inviteGroup(

return ResponseEntity.ok(
ApiSpec.empty(
SuccessCode.SUCCESS
SuccessCode.ACCEPTED
)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package site.timecapsulearchive.core.domain.group.data.dto;

public record GroupOwnerSummaryDto(
String nickname,
Boolean isOwner,
String groupProfileUrl
) {

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<GroupSummaryDto> findGroupsSlice(
Slice<GroupSummaryDto> findGroupsSlice(
final Long memberId,
final int size,
final ZonedDateTime createdAt
) {
final List<GroupSummaryDto> 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<GroupDetailDto> findGroupDetailByGroupId(final Long groupId);

return new SliceImpl<>(groups, Pageable.ofSize(size), groups.size() > size);
}

public Optional<GroupDetailDto> 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)
);
}
}
Original file line number Diff line number Diff line change
@@ -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<GroupSummaryDto> findGroupsSlice(
final Long memberId,
final int size,
final ZonedDateTime createdAt
) {
final List<GroupSummaryDto> 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<GroupDetailDto> 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)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.data.repository.Repository;
import site.timecapsulearchive.core.domain.group.entity.Group;

public interface GroupRepository extends Repository<Group, Long> {
public interface GroupRepository extends Repository<Group, Long>, GroupQueryRepository {

void save(Group group);

Expand Down
Original file line number Diff line number Diff line change
@@ -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<GroupOwnerSummaryDto> findOwnerInMemberGroup(Long groupId, Long memberId);

}
Original file line number Diff line number Diff line change
@@ -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<GroupOwnerSummaryDto> 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()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import org.springframework.data.repository.Repository;
import site.timecapsulearchive.core.domain.group.entity.MemberGroup;

public interface MemberGroupRepository extends Repository<MemberGroup, Long> {
public interface MemberGroupRepository extends Repository<MemberGroup, Long>, MemberGroupQueryRepository{

void save(MemberGroup memberGroup);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -32,12 +29,12 @@ public Slice<GroupSummaryDto> 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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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", "친구 요청을 찾지 못하였습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4d0dcda

Please sign in to comment.