diff --git a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/GroupInfoController.java b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/GroupInfoController.java index 49f1dcad..7a886577 100644 --- a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/GroupInfoController.java +++ b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/GroupInfoController.java @@ -2,6 +2,7 @@ import com.heachi.admin.common.response.JsonResult; import com.heachi.external.clients.auth.response.UserInfoResponse; +import com.heachi.housework.api.controller.group.info.request.GroupInfoRegisterRequest; import com.heachi.housework.api.service.auth.AuthExternalService; import com.heachi.housework.api.service.group.info.GroupInfoService; import lombok.RequiredArgsConstructor; @@ -62,4 +63,15 @@ public JsonResult joinGroupInfo(@RequestHeader(name = "Authorization") String return JsonResult.successOf("성공적으로 그룹에 가입했습니다."); } + + // 그룹 가입 요청 처리 + @PostMapping("/register") + public JsonResult joinGroupRequestHandler(@RequestHeader(name = "Authorization") String authorization, + @Valid @RequestBody GroupInfoRegisterRequest request) { + // Auth 서버에서 사용자 인증 + UserInfoResponse userInfo = authExternalService.userAuthenticate(authorization); + groupInfoService.joinRequestHandler(userInfo.getEmail(), request); + + return JsonResult.successOf("그룹 가입 요청을 성공적으로 수행했습니다."); + } } diff --git a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/request/GroupInfoRegisterRequest.java b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/request/GroupInfoRegisterRequest.java new file mode 100644 index 00000000..101ad3a6 --- /dev/null +++ b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/request/GroupInfoRegisterRequest.java @@ -0,0 +1,20 @@ +package com.heachi.housework.api.controller.group.info.request; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Getter +public class GroupInfoRegisterRequest { + private Long groupMemberId; + private Long groupId; + @NotEmpty + private GroupInfoRegisterRequestStatusEnum status; + + @Builder + private GroupInfoRegisterRequest(Long groupMemberId, Long groupId, GroupInfoRegisterRequestStatusEnum status) { + this.groupMemberId = groupMemberId; + this.groupId = groupId; + this.status = status; + } +} diff --git a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/request/GroupInfoRegisterRequestStatusEnum.java b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/request/GroupInfoRegisterRequestStatusEnum.java new file mode 100644 index 00000000..7ba72d7b --- /dev/null +++ b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/group/info/request/GroupInfoRegisterRequestStatusEnum.java @@ -0,0 +1,6 @@ +package com.heachi.housework.api.controller.group.info.request; + +public enum GroupInfoRegisterRequestStatusEnum { + ACCEPT, + REFUSE +} diff --git a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/housework/info/HouseworkInfoController.java b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/housework/info/HouseworkInfoController.java index ad6944c7..ebdf8767 100644 --- a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/housework/info/HouseworkInfoController.java +++ b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/controller/housework/info/HouseworkInfoController.java @@ -6,6 +6,7 @@ import com.heachi.housework.api.service.auth.AuthExternalService; import com.heachi.housework.api.service.housework.info.HouseworkInfoService; import com.heachi.housework.api.service.housework.info.request.HouseworkInfoCreateServiceRequest; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -21,7 +22,7 @@ public class HouseworkInfoController { @PostMapping("/{groupId}") public JsonResult createHouseworkInfo(@RequestHeader(name = "Authorization") String authorization, @PathVariable(name = "groupId") Long groupId, - @RequestBody HouseworkInfoCreateRequest request + @Valid @RequestBody HouseworkInfoCreateRequest request ) { // Auth 서버로 요청자 인증 요청 - 해당 그룹원인지 판별하고 상태가 ACCEPT인지 확인 try { diff --git a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/service/group/info/GroupInfoService.java b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/service/group/info/GroupInfoService.java index 623bb575..ea36f62e 100644 --- a/heachi-core/housework-api/src/main/java/com/heachi/housework/api/service/group/info/GroupInfoService.java +++ b/heachi-core/housework-api/src/main/java/com/heachi/housework/api/service/group/info/GroupInfoService.java @@ -2,6 +2,9 @@ import com.heachi.admin.common.exception.ExceptionMessage; import com.heachi.admin.common.exception.group.info.GroupInfoException; +import com.heachi.admin.common.exception.group.member.GroupMemberException; +import com.heachi.housework.api.controller.group.info.request.GroupInfoRegisterRequest; +import com.heachi.housework.api.controller.group.info.request.GroupInfoRegisterRequestStatusEnum; import com.heachi.housework.api.service.group.info.response.GroupInfoUserGroupServiceResponse; import com.heachi.mysql.define.group.info.repository.GroupInfoRepository; import com.heachi.mysql.define.group.info.repository.response.GroupInfoUserGroupResponse; @@ -28,6 +31,9 @@ import java.util.Optional; import java.util.stream.Collectors; +import static com.heachi.mysql.define.group.info.QGroupInfo.groupInfo; +import static com.heachi.mysql.define.user.QUser.user; + @Slf4j @Service @Transactional(readOnly = true) @@ -156,4 +162,42 @@ public void joinGroupInfo(String email, String joinCode) { } } + + @Transactional + public void joinRequestHandler(String adminEmail, GroupInfoRegisterRequest request) { + // 그룹장의 email과 GroupId로 GROUP_MEMBER 조회 + GroupMember adminGroupMember = groupMemberRepository.findGroupMemberByUserEmailAndGroupInfoId(adminEmail, request.getGroupId()).orElseThrow(() -> { + log.warn(">>>> 해당 그룹의 구성원이 아닙니다. : [{}]", adminEmail); + + throw new GroupMemberException(ExceptionMessage.GROUP_MEMBER_NOT_FOUND); + }); + + // role이 GROUP_ADMIN(그룹장)인지 확인 + if (adminGroupMember.getRole() != GroupMemberRole.GROUP_ADMIN) { + log.warn(">>>> 해당 그룹의 그룹장이 아닙니다. : [role: {}]", adminGroupMember.getRole()); + + throw new GroupMemberException(ExceptionMessage.GROUP_MEMBER_ROLE_NOT_ADMIN); + } + + // 그룹 가입 요청자 조회 + GroupMember requestGroupMember = groupMemberRepository.findGroupMemberByGroupMemberIdAndGroupInfoId(request.getGroupMemberId(), request.getGroupId()).orElseThrow(() -> { + log.warn(">>>> Group Member Not Found : [{}]", request.getGroupMemberId()); + + throw new GroupMemberException(ExceptionMessage.GROUP_MEMBER_NOT_FOUND); + }); + + // 그룹 가입 요청자 상태 확인: WAITING 상태가 아닐경우 예외처리 + if (requestGroupMember.getStatus() != GroupMemberStatus.WAITING) { + log.warn(">>>> Group Member's Status Is Not WAITING : [{}]", requestGroupMember.getStatus()); + + throw new GroupMemberException(ExceptionMessage.GROUP_MEMBER_STATUS_NOT_WAITING); + } + + // status에 따른 그룹 가입 요청 핸들링 + if (request.getStatus().equals(GroupInfoRegisterRequestStatusEnum.ACCEPT)) { + requestGroupMember.acceptJoin(); + } else { + requestGroupMember.refuseJoin(); + } + } } diff --git a/heachi-core/housework-api/src/test/java/com/heachi/housework/api/service/group/info/GroupInfoServiceTest.java b/heachi-core/housework-api/src/test/java/com/heachi/housework/api/service/group/info/GroupInfoServiceTest.java index c8eb4eed..e5241fe1 100644 --- a/heachi-core/housework-api/src/test/java/com/heachi/housework/api/service/group/info/GroupInfoServiceTest.java +++ b/heachi-core/housework-api/src/test/java/com/heachi/housework/api/service/group/info/GroupInfoServiceTest.java @@ -1,6 +1,11 @@ package com.heachi.housework.api.service.group.info; +import com.heachi.admin.common.exception.ExceptionMessage; +import com.heachi.admin.common.exception.group.info.GroupInfoException; +import com.heachi.admin.common.exception.group.member.GroupMemberException; import com.heachi.housework.TestConfig; +import com.heachi.housework.api.controller.group.info.request.GroupInfoRegisterRequest; +import com.heachi.housework.api.controller.group.info.request.GroupInfoRegisterRequestStatusEnum; import com.heachi.housework.api.service.group.info.response.GroupInfoUserGroupServiceResponse; import com.heachi.admin.common.exception.user.UserException; import com.heachi.housework.TestConfig; @@ -21,6 +26,8 @@ import com.heachi.mysql.define.housework.todo.repository.HouseworkTodoRepository; import com.heachi.mysql.define.user.User; +import com.heachi.mysql.define.user.constant.UserPlatformType; +import com.heachi.mysql.define.user.constant.UserRole; import com.heachi.mysql.define.user.repository.UserRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; @@ -244,4 +251,190 @@ void joinGroupInfoAlreadyGroupMemberStatusWITHDRAW() { assertThat(findGroupMember.getStatus()).isEqualTo(GroupMemberStatus.WAITING); assertThat(findGroupMember.getRole()).isEqualTo(GroupMemberRole.GROUP_MEMBER); } + + @Test + @DisplayName("그룹 가입 요청자의 상태가 WAITING이 아닐 경우 예외가 발생한다.") + void joinRequestGroupMemberStatusIsNotWAITING() { + // given + // Admin + User admin = userRepository.save(generateUser()); + GroupInfo groupInfo = groupInfoRepository.save(generateGroupInfo(admin)); + + GroupMember adminMember = GroupMember.builder() + .groupInfo(groupInfo) + .role(GroupMemberRole.GROUP_ADMIN) + .status(GroupMemberStatus.ACCEPT) + .user(admin) + .build(); + groupMemberRepository.save(adminMember); + + // WAITING 상태가 아닌 그룹 멤버 + User requestUser = userRepository.save(User.builder() + .platformId("11") + .platformType(UserPlatformType.KAKAO) + .role(UserRole.USER) + .name("test") + .email("test@test.com") + .phoneNumber("010-1233-0000") + .profileImageUrl("https://google.com") + .pushAlarmYn(true) + .build()); + GroupMember requestMember = GroupMember.builder() + .groupInfo(groupInfo) + .role(GroupMemberRole.GROUP_MEMBER) + .status(GroupMemberStatus.ACCEPT) + .user(requestUser) + .build(); + GroupMember requestWaitingMember = groupMemberRepository.save(requestMember); + + GroupInfoRegisterRequest request = GroupInfoRegisterRequest.builder() + .groupMemberId(requestWaitingMember.getId()) + .groupId(groupInfo.getId()) + .status(GroupInfoRegisterRequestStatusEnum.REFUSE) + .build(); + + // when & then + GroupMemberException exception = assertThrows(GroupMemberException.class, () -> groupInfoService.joinRequestHandler(admin.getEmail(), request)); + assertThat(exception.getMessage()).isEqualTo(ExceptionMessage.GROUP_MEMBER_STATUS_NOT_WAITING.getText()); + + } + + @Test + @DisplayName("해당 그룹의 그룹장이 아닐 경우 예외가 발생한다.") + void joinResponseGroupMemberRoleIsNotAdmin() { + // given + // Admin이 아닌 그룹 멤버 + User notAdmin = userRepository.save(User.builder() + .platformId("11") + .platformType(UserPlatformType.KAKAO) + .role(UserRole.USER) + .name("test") + .email("test@test.com") + .phoneNumber("010-1233-0000") + .profileImageUrl("https://google.com") + .pushAlarmYn(true) + .build()); + GroupInfo groupInfo = groupInfoRepository.save(generateGroupInfo(notAdmin)); + groupMemberRepository.save(generateGroupMember(notAdmin, groupInfo)); + + // WAITING 상태의 그룹 멤버 + User requestUser = userRepository.save(generateUser()); + GroupMember requestMember = GroupMember.builder() + .groupInfo(groupInfo) + .role(GroupMemberRole.GROUP_MEMBER) + .status(GroupMemberStatus.WAITING) + .user(requestUser) + .build(); + GroupMember requestWaitingMember = groupMemberRepository.save(requestMember); + + GroupInfoRegisterRequest request = GroupInfoRegisterRequest.builder() + .groupMemberId(requestWaitingMember.getId()) + .groupId(groupInfo.getId()) + .status(GroupInfoRegisterRequestStatusEnum.ACCEPT) + .build(); + + // when & then + GroupMemberException exception = assertThrows(GroupMemberException.class, () -> groupInfoService.joinRequestHandler(notAdmin.getEmail(), request)); + assertThat(exception.getMessage()).isEqualTo(ExceptionMessage.GROUP_MEMBER_ROLE_NOT_ADMIN.getText()); + + } + + @Test + @DisplayName("status가 true일 경우 그룹 가입 요청이 승인되어 그룹 가입 요청자의 상태가 ACCEPT로 변경된다.") + void joinRequestStatusIsTrue() { + // Admin + User admin = userRepository.save(generateUser()); + GroupInfo groupInfo = groupInfoRepository.save(generateGroupInfo(admin)); + + GroupMember adminMember = GroupMember.builder() + .groupInfo(groupInfo) + .role(GroupMemberRole.GROUP_ADMIN) + .status(GroupMemberStatus.ACCEPT) + .user(admin) + .build(); + groupMemberRepository.save(adminMember); + + // WAITING 상태의 그룹 멤버 + User requestUser = userRepository.save(User.builder() + .platformId("11") + .platformType(UserPlatformType.KAKAO) + .role(UserRole.USER) + .name("test") + .email("test@test.com") + .phoneNumber("010-1233-0000") + .profileImageUrl("https://google.com") + .pushAlarmYn(true) + .build()); + GroupMember requestMember = GroupMember.builder() + .groupInfo(groupInfo) + .role(GroupMemberRole.GROUP_MEMBER) + .status(GroupMemberStatus.WAITING) + .user(requestUser) + .build(); + GroupMember requestWaitingMember = groupMemberRepository.save(requestMember); + + GroupInfoRegisterRequest request = GroupInfoRegisterRequest.builder() + .groupMemberId(requestWaitingMember.getId()) + .groupId(groupInfo.getId()) + .status(GroupInfoRegisterRequestStatusEnum.ACCEPT) + .build(); + + // when + groupInfoService.joinRequestHandler(admin.getEmail(), request); + + + // then + GroupMember updateMember = groupMemberRepository.findById(requestWaitingMember.getId()).get(); + assertThat(updateMember.getStatus()).isEqualTo(GroupMemberStatus.ACCEPT); + } + + @Test + @DisplayName("status가 false일 경우 그룹 가입 요청이 거절되어 그룹 가입 요청자의 상태가 WITHDRAW로 변경된다.") + void joinRequestStatusIsFalse() { + // Admin + User admin = userRepository.save(generateUser()); + GroupInfo groupInfo = groupInfoRepository.save(generateGroupInfo(admin)); + + GroupMember adminMember = GroupMember.builder() + .groupInfo(groupInfo) + .role(GroupMemberRole.GROUP_ADMIN) + .status(GroupMemberStatus.ACCEPT) + .user(admin) + .build(); + groupMemberRepository.save(adminMember); + + // WAITING 상태의 그룹 멤버 + User requestUser = userRepository.save(User.builder() + .platformId("11") + .platformType(UserPlatformType.KAKAO) + .role(UserRole.USER) + .name("test") + .email("test@test.com") + .phoneNumber("010-1233-0000") + .profileImageUrl("https://google.com") + .pushAlarmYn(true) + .build()); + GroupMember requestMember = GroupMember.builder() + .groupInfo(groupInfo) + .role(GroupMemberRole.GROUP_MEMBER) + .status(GroupMemberStatus.WAITING) + .user(requestUser) + .build(); + GroupMember requestWaitingMember = groupMemberRepository.save(requestMember); + + GroupInfoRegisterRequest request = GroupInfoRegisterRequest.builder() + .groupMemberId(requestWaitingMember.getId()) + .groupId(groupInfo.getId()) + .status(GroupInfoRegisterRequestStatusEnum.REFUSE) + .build(); + + // when + groupInfoService.joinRequestHandler(admin.getEmail(), request); + + + // then + GroupMember updateMember = groupMemberRepository.findById(requestWaitingMember.getId()).get(); + assertThat(updateMember.getStatus()).isEqualTo(GroupMemberStatus.WITHDRAW); + } + } \ No newline at end of file diff --git a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/GroupMember.java b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/GroupMember.java index 72164460..6019ac3f 100644 --- a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/GroupMember.java +++ b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/GroupMember.java @@ -52,4 +52,14 @@ public void rejoinGroup() { this.role = GroupMemberRole.GROUP_MEMBER; this.status = GroupMemberStatus.WAITING; } + + // 그룹 가입 요청을 승인받았을 경우 + public void acceptJoin() { + this.status = GroupMemberStatus.ACCEPT; + } + + // 그룹 가입 요청을 거절당했을 경우 + public void refuseJoin() { + this.status = GroupMemberStatus.WITHDRAW; + } } diff --git a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepository.java b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepository.java index e2eb176b..075871c5 100644 --- a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepository.java +++ b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepository.java @@ -4,6 +4,8 @@ import com.heachi.mysql.define.group.member.GroupMember; import com.heachi.mysql.define.user.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; @@ -12,4 +14,9 @@ public interface GroupMemberRepository extends JpaRepository, Optional findByUser(User user); Optional findByUserAndGroupInfo(User user, GroupInfo groupInfo); + + Optional findByUserAndGroupInfo_id(User user, Long groupId); + + @Query("select gm from GROUP_MEMBER gm where gm.id = :id and gm.groupInfo.id = :groupId") + Optional findByIdAndGroupInfoId(@Param("id")Long groupMemberId, @Param("groupId")Long groupId); } diff --git a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryCustom.java b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryCustom.java index 84cfaa79..d591b571 100644 --- a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryCustom.java +++ b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryCustom.java @@ -1,6 +1,7 @@ package com.heachi.mysql.define.group.member.repository; import com.heachi.mysql.define.group.member.GroupMember; +import com.heachi.mysql.define.user.User; import java.util.List; import java.util.Optional; @@ -19,4 +20,10 @@ public interface GroupMemberRepositoryCustom { // Email과 todoId를 통해 GroupMember를 조회한다. public Optional findGroupMemberByUserEmailAndTodoId(String email, Long todoId); + // 그룹 멤버의 ID와 User 정보를 이용해 그룹 멤버를 조회한다. + public Optional findGroupMemberByGroupMemberIdAndGroupInfoId(Long groupMemberId, Long groupId); + + // User의 email과 GroupId를 이용해 그룹 멤버를 조회한다. + public Optional findGroupMemberByUserEmailAndGroupInfoId(String userEmail, Long groupId); + } diff --git a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryImpl.java b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryImpl.java index 827a5fab..d3ceff90 100644 --- a/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryImpl.java +++ b/heachi-domain-mysql/src/main/java/com/heachi/mysql/define/group/member/repository/GroupMemberRepositoryImpl.java @@ -3,6 +3,7 @@ import com.heachi.mysql.define.group.info.QGroupInfo; import com.heachi.mysql.define.group.member.GroupMember; import com.heachi.mysql.define.group.member.constant.GroupMemberStatus; +import com.heachi.mysql.define.user.User; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -69,7 +70,26 @@ public Optional findGroupMemberByUserEmailAndTodoId(String email, L .from(houseworkTodo) .innerJoin(houseworkTodo.groupInfo, groupInfo) .where(houseworkTodo.id.eq(todoId))) - .and(user.email.eq(email))) + .and(user.email.eq(email))); + } + + @Override + public Optional findGroupMemberByGroupMemberIdAndGroupInfoId(Long groupMemberId, Long groupId) { + // select gm from groupMember gm where gm.id= :groupMemberId and gm.groupInfo.id= :groupId + return Optional.of(queryFactory.selectFrom(groupMember) + .innerJoin(groupMember.groupInfo, groupInfo).fetchJoin() + .where(groupMember.id.eq(groupMemberId) + .and(groupMember.groupInfo.id.eq(groupId))) + .fetchOne()); + } + + @Override + public Optional findGroupMemberByUserEmailAndGroupInfoId(String userEmail, Long groupId) { + return Optional.of(queryFactory.selectFrom(groupMember) + .innerJoin(groupMember.user, user).fetchJoin() + .innerJoin(groupMember.groupInfo, groupInfo).fetchJoin() + .where(groupMember.user.email.eq(userEmail) + .and(groupMember.groupInfo.id.eq(groupId))) .fetchOne()); } } diff --git a/heachi-support/common/src/main/java/com/heachi/admin/common/exception/ExceptionMessage.java b/heachi-support/common/src/main/java/com/heachi/admin/common/exception/ExceptionMessage.java index 97e31769..8047a53a 100644 --- a/heachi-support/common/src/main/java/com/heachi/admin/common/exception/ExceptionMessage.java +++ b/heachi-support/common/src/main/java/com/heachi/admin/common/exception/ExceptionMessage.java @@ -45,6 +45,8 @@ public enum ExceptionMessage { // GroupMemberException GROUP_MEMBER_NOT_FOUND("그룹 멤버를 찾지 못했습니다."), + GROUP_MEMBER_STATUS_NOT_WAITING("그룹 가입 요청 수락 대기자가 아닙니다."), + GROUP_MEMBER_ROLE_NOT_ADMIN("그룹의 그룹장이 아닙니다."), // NotifyException NOTIFY_NOT_FOUND("해당 아이디의 알림을 찾을 수 없습니다."),