diff --git a/src/main/java/flipnote/group/adapter/in/web/JoinController.java b/src/main/java/flipnote/group/adapter/in/web/JoinController.java index a876c17..3a23713 100644 --- a/src/main/java/flipnote/group/adapter/in/web/JoinController.java +++ b/src/main/java/flipnote/group/adapter/in/web/JoinController.java @@ -3,6 +3,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +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; @@ -11,22 +12,28 @@ import org.springframework.web.bind.annotation.RestController; import flipnote.group.api.dto.request.ApplicationFormRequestDto; +import flipnote.group.api.dto.request.JoinRespondRequestDto; import flipnote.group.api.dto.response.ApplicationFormResponseDto; import flipnote.group.api.dto.response.FindJoinFormListResponseDto; +import flipnote.group.api.dto.response.JoinRespondResponseDto; +import flipnote.group.application.port.in.JoinRespondUseCase; import flipnote.group.application.port.in.JoinUseCase; import flipnote.group.application.port.in.command.ApplicationFormCommand; import flipnote.group.application.port.in.command.FindJoinFormCommand; +import flipnote.group.application.port.in.command.JoinRespondCommand; import flipnote.group.application.port.in.result.ApplicationFormResult; import flipnote.group.application.port.in.result.FindJoinFormListResult; +import flipnote.group.application.port.in.result.JoinRespondResult; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/v1/groups/{groupId}") +@RequestMapping("/v1/groups/{groupId}/joins") @RequiredArgsConstructor public class JoinController { private final JoinUseCase joinUseCase; + private final JoinRespondUseCase joinRespondUseCase; /** * 가입 신청 요청 @@ -35,7 +42,7 @@ public class JoinController { * @param req * @return */ - @PostMapping("/joins") + @PostMapping("") public ResponseEntity joinRequest( @RequestHeader("X-USER-ID") Long userId, @PathVariable("groupId") Long groupId, @@ -57,7 +64,7 @@ public ResponseEntity joinRequest( * @param groupId * @return */ - @GetMapping("/joins") + @GetMapping("") public ResponseEntity findGroupJoinList( @RequestHeader("X-USER-ID") Long userId, @PathVariable("groupId") Long groupId) { @@ -68,12 +75,32 @@ public ResponseEntity findGroupJoinList( FindJoinFormListResponseDto res = FindJoinFormListResponseDto.from(result); - return ResponseEntity.status(HttpStatus.CREATED).body(res); + return ResponseEntity.ok(res); + } + + /** + * 가입 신청 수락 여부 응답 + * @param userId + * @param groupId + * @param joinId + * @param req + * @return + */ + @PatchMapping("/{joinId}") + public ResponseEntity respondToJoinRequest( + @RequestHeader("X-USER-ID") Long userId, + @PathVariable("groupId") Long groupId, + @PathVariable("joinId") Long joinId, + @Valid @RequestBody JoinRespondRequestDto req) { + + JoinRespondCommand cmd = new JoinRespondCommand(groupId, userId, joinId, req.joinStatus()); + + JoinRespondResult result = joinRespondUseCase.joinRespond(cmd); + + JoinRespondResponseDto res = JoinRespondResponseDto.of(result); + + return ResponseEntity.ok(res); } - - //todo 가입신청 응답 - - //todo 가입신청 삭제 - + //todo 내가 신청한 가입신청 리스트 조회 } diff --git a/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java index a6381fb..617b559 100644 --- a/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java +++ b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java @@ -135,4 +135,22 @@ private static void validate(CreateGroupCommand cmd) { throw new IllegalArgumentException("name too long"); } } + + public void plusCount() { + + if(this.memberCount+1 > this.maxMember) { + throw new IllegalArgumentException("max member"); + } + + this.memberCount++; + } + + public void minusCount() { + + if(this.memberCount-1 < 0) { + throw new IllegalArgumentException("not minus member"); + } + + this.memberCount--; + } } diff --git a/src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java b/src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java index 5de96e5..ae6ca5f 100644 --- a/src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java +++ b/src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java @@ -41,7 +41,8 @@ public class JoinEntity extends BaseEntity { private String form; @Builder - private JoinEntity(Long userId, Long groupId, JoinStatus status, String form) { + private JoinEntity(Long id, Long userId, Long groupId, JoinStatus status, String form) { + this.id = id; this.userId = userId; this.groupId = groupId; this.status = status; @@ -56,4 +57,8 @@ public static JoinEntity create(Long groupId, Long userId, String form, JoinStat .status(status) .build(); } + + public void updateStatus(JoinStatus status) { + this.status = status; + } } diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java index 72ac2c2..6fc6b34 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java @@ -5,11 +5,13 @@ import org.springframework.stereotype.Repository; +import flipnote.group.adapter.out.entity.GroupEntity; import flipnote.group.adapter.out.entity.GroupMemberEntity; import flipnote.group.application.port.out.GroupMemberRepositoryPort; import flipnote.group.domain.model.member.GroupMemberRole; import flipnote.group.domain.model.member.MemberInfo; import flipnote.group.infrastructure.persistence.jpa.GroupMemberRepository; +import flipnote.group.infrastructure.persistence.jpa.GroupRepository; import lombok.RequiredArgsConstructor; @Repository @@ -17,6 +19,7 @@ public class GroupMemberRepositoryAdapter implements GroupMemberRepositoryPort { private final GroupMemberRepository groupMemberRepository; + private final GroupRepository groupRepository; /** * 그룹 멤버 저장 @@ -25,6 +28,13 @@ public class GroupMemberRepositoryAdapter implements GroupMemberRepositoryPort { @Override public void save(GroupMemberEntity groupMember) { groupMemberRepository.save(groupMember); + + GroupEntity groupEntity = groupRepository.findByIdForUpdate(groupMember.getGroupId()).orElseThrow( + () -> new IllegalArgumentException("not exist group") + ); + + //그룹 엔티티 + 1 + groupEntity.plusCount(); } /** diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java index 31ac079..aa6c087 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java @@ -8,7 +8,7 @@ import flipnote.group.application.port.out.GroupRepositoryPort; import flipnote.group.domain.model.group.Category; import flipnote.group.domain.model.group.GroupInfo; -import flipnote.group.infrastructure.persistence.querydsl.GroupRepository; +import flipnote.group.infrastructure.persistence.jpa.GroupRepository; import lombok.RequiredArgsConstructor; @Repository @@ -57,4 +57,17 @@ public List findAllByCursorAndUserId(Long cursorId, Category category public List findAllByCursorAndCreatedUserId(Long cursorId, Category category, int size, Long userId) { return findAllByCursorAndCreatedUserId(cursorId, category, size, userId); } + + @Override + public boolean checkJoinable(Long groupId) { + + GroupEntity groupEntity = groupRepository.findByIdForUpdate(groupId).orElseThrow( + () -> new IllegalArgumentException("not exists") + ); + + int maxMember = groupEntity.getMaxMember(); + int count = groupEntity.getMemberCount(); + + return maxMember > count; + } } diff --git a/src/main/java/flipnote/group/adapter/out/persistence/JoinRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/JoinRepositoryAdapter.java index a24d011..17b9685 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/JoinRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/JoinRepositoryAdapter.java @@ -32,4 +32,18 @@ public List findFormList(Long groupId) { return joinList; } + + @Override + public JoinEntity findJoin(Long joinId) { + + JoinEntity entity = joinRepository.findById(joinId).orElseThrow( + () -> new IllegalArgumentException("not exist") + ); + return entity; + } + + @Override + public JoinEntity updateJoin(JoinEntity join) { + return joinRepository.save(join); + } } diff --git a/src/main/java/flipnote/group/api/dto/request/JoinRespondRequestDto.java b/src/main/java/flipnote/group/api/dto/request/JoinRespondRequestDto.java new file mode 100644 index 0000000..fd86505 --- /dev/null +++ b/src/main/java/flipnote/group/api/dto/request/JoinRespondRequestDto.java @@ -0,0 +1,15 @@ +package flipnote.group.api.dto.request; + +import flipnote.group.domain.model.join.JoinStatus; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +public record JoinRespondRequestDto( + @NotNull(message = "그룹 신청 상태를 선택해주세요.") + @Pattern(regexp = "ACCEPT|REJECT", message = "ACCEPT 또는 REJECT만 입력 가능합니다.") + String status +) { + public JoinStatus joinStatus() { + return JoinStatus.valueOf(status); + } +} diff --git a/src/main/java/flipnote/group/api/dto/response/JoinRespondResponseDto.java b/src/main/java/flipnote/group/api/dto/response/JoinRespondResponseDto.java new file mode 100644 index 0000000..12a935d --- /dev/null +++ b/src/main/java/flipnote/group/api/dto/response/JoinRespondResponseDto.java @@ -0,0 +1,17 @@ +package flipnote.group.api.dto.response; + +import flipnote.group.application.port.in.result.JoinRespondResult; +import flipnote.group.domain.model.join.JoinStatus; + +public record JoinRespondResponseDto( + Long joinId, + JoinStatus status +) { + public static JoinRespondResponseDto of(JoinRespondResult result) { + + Long joinId = result.join().getId(); + JoinStatus status = result.join().getStatus(); + + return new JoinRespondResponseDto(joinId, status); + } +} diff --git a/src/main/java/flipnote/group/application/port/in/JoinRespondUseCase.java b/src/main/java/flipnote/group/application/port/in/JoinRespondUseCase.java new file mode 100644 index 0000000..b96c101 --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/JoinRespondUseCase.java @@ -0,0 +1,8 @@ +package flipnote.group.application.port.in; + +import flipnote.group.application.port.in.command.JoinRespondCommand; +import flipnote.group.application.port.in.result.JoinRespondResult; + +public interface JoinRespondUseCase { + JoinRespondResult joinRespond(JoinRespondCommand cmd); +} diff --git a/src/main/java/flipnote/group/application/port/in/command/JoinRespondCommand.java b/src/main/java/flipnote/group/application/port/in/command/JoinRespondCommand.java new file mode 100644 index 0000000..4b2719d --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/command/JoinRespondCommand.java @@ -0,0 +1,11 @@ +package flipnote.group.application.port.in.command; + +import flipnote.group.domain.model.join.JoinStatus; + +public record JoinRespondCommand( + Long groupId, + Long userId, + Long joinId, + JoinStatus status +) { +} diff --git a/src/main/java/flipnote/group/application/port/in/result/JoinRespondResult.java b/src/main/java/flipnote/group/application/port/in/result/JoinRespondResult.java new file mode 100644 index 0000000..83d31ed --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/result/JoinRespondResult.java @@ -0,0 +1,11 @@ +package flipnote.group.application.port.in.result; + +import flipnote.group.adapter.out.entity.JoinEntity; + +public record JoinRespondResult( + JoinEntity join +) { + public static JoinRespondResult of(JoinEntity join) { + return new JoinRespondResult(join); + } +} diff --git a/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java b/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java index b3c4779..b57a4e6 100644 --- a/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java +++ b/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java @@ -19,4 +19,6 @@ public interface GroupRepositoryPort { List findAllByCursorAndUserId(Long cursorId, Category category, int size, Long userId); List findAllByCursorAndCreatedUserId(Long cursorId, Category category, int size, Long userId); + + boolean checkJoinable(Long groupId); } diff --git a/src/main/java/flipnote/group/application/port/out/JoinRepositoryPort.java b/src/main/java/flipnote/group/application/port/out/JoinRepositoryPort.java index 3a07788..7d99135 100644 --- a/src/main/java/flipnote/group/application/port/out/JoinRepositoryPort.java +++ b/src/main/java/flipnote/group/application/port/out/JoinRepositoryPort.java @@ -10,4 +10,8 @@ public interface JoinRepositoryPort { void save(JoinEntity join); List findFormList(Long groupId); + + JoinEntity findJoin(Long joinId); + + JoinEntity updateJoin(JoinEntity join); } diff --git a/src/main/java/flipnote/group/application/service/ChangeGroupService.java b/src/main/java/flipnote/group/application/service/ChangeGroupService.java index d86b098..c48eba7 100644 --- a/src/main/java/flipnote/group/application/service/ChangeGroupService.java +++ b/src/main/java/flipnote/group/application/service/ChangeGroupService.java @@ -9,7 +9,7 @@ import flipnote.group.application.port.in.command.ChangeGroupCommand; import flipnote.group.application.port.in.result.ChangeGroupResult; import flipnote.group.domain.model.member.GroupMemberRole; -import flipnote.group.infrastructure.persistence.querydsl.GroupRepository; +import flipnote.group.infrastructure.persistence.jpa.GroupRepository; import flipnote.image.grpc.v1.GetUrlByReferenceRequest; import flipnote.image.grpc.v1.GetUrlByReferenceResponse; import flipnote.image.grpc.v1.ImageCommandServiceGrpc; diff --git a/src/main/java/flipnote/group/application/service/JoinRespondService.java b/src/main/java/flipnote/group/application/service/JoinRespondService.java new file mode 100644 index 0000000..0c2a226 --- /dev/null +++ b/src/main/java/flipnote/group/application/service/JoinRespondService.java @@ -0,0 +1,83 @@ +package flipnote.group.application.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import flipnote.group.adapter.out.entity.GroupMemberEntity; +import flipnote.group.adapter.out.entity.JoinEntity; +import flipnote.group.adapter.out.entity.RoleEntity; +import flipnote.group.application.port.in.JoinRespondUseCase; +import flipnote.group.application.port.in.command.JoinRespondCommand; +import flipnote.group.application.port.in.result.JoinRespondResult; +import flipnote.group.application.port.out.GroupMemberRepositoryPort; +import flipnote.group.application.port.out.GroupRepositoryPort; +import flipnote.group.application.port.out.GroupRoleRepositoryPort; +import flipnote.group.application.port.out.JoinRepositoryPort; +import flipnote.group.domain.model.join.JoinStatus; +import flipnote.group.domain.model.member.GroupMemberRole; +import flipnote.group.domain.model.permission.GroupPermission; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class JoinRespondService implements JoinRespondUseCase { + + private final JoinRepositoryPort joinRepository; + private final GroupRoleRepositoryPort groupRoleRepository; + private final GroupRepositoryPort groupRepository; + private final GroupMemberRepositoryPort groupMemberRepository; + + private static final GroupPermission MANAGE = GroupPermission.JOIN_REQUEST_MANAGE; + + /** + * 가입 신청 응답 + * @param cmd + * @return + */ + @Override + @Transactional + public JoinRespondResult joinRespond(JoinRespondCommand cmd) { + + //권한 체크 + boolean existPermission = groupRoleRepository.checkPermission(cmd.userId(), cmd.groupId(), MANAGE); + + if(!existPermission) { + throw new IllegalArgumentException("not permission"); + } + + JoinEntity join = joinRepository.findJoin(cmd.joinId()); + + if(join.getStatus().equals(JoinStatus.ACCEPT)) { + throw new IllegalArgumentException("already accept"); + } + + join.updateStatus(cmd.status()); + + //거절일 경우 + if(cmd.status().equals(JoinStatus.REJECT)) { + JoinEntity response = joinRepository.updateJoin(join); + + return new JoinRespondResult(response); + } + + //수락일 경우 + boolean joinable = groupRepository.checkJoinable(cmd.groupId()); + + //꽉찼을 경우 + if(!joinable) { + throw new IllegalArgumentException("max member"); + } + + //가입 가능한 경우 + RoleEntity role = groupRoleRepository.findByIdAndRole(cmd.groupId(), GroupMemberRole.MEMBER); + + //주의: cmd의 유저가 아닌 join의 아이디로 해야함 + GroupMemberEntity groupMember = GroupMemberEntity.create(cmd.groupId(), join.getUserId(), role); + + groupMemberRepository.save(groupMember); + + JoinEntity response = joinRepository.updateJoin(join); + + return new JoinRespondResult(response); + } +} diff --git a/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java new file mode 100644 index 0000000..327b40a --- /dev/null +++ b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java @@ -0,0 +1,20 @@ +package flipnote.group.infrastructure.persistence.jpa; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import flipnote.group.adapter.out.entity.GroupEntity; +import flipnote.group.infrastructure.persistence.querydsl.GroupRepositoryCustom; +import jakarta.persistence.LockModeType; + +public interface GroupRepository extends JpaRepository, GroupRepositoryCustom { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("select g from GroupEntity g where g.id = :id") + Optional findByIdForUpdate(@Param("id") Long id); + +} diff --git a/src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepository.java b/src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepository.java deleted file mode 100644 index 0ad9701..0000000 --- a/src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package flipnote.group.infrastructure.persistence.querydsl; - -import org.springframework.data.jpa.repository.JpaRepository; - -import flipnote.group.adapter.out.entity.GroupEntity; - -public interface GroupRepository extends JpaRepository, GroupRepositoryCustom { -}