Skip to content

Commit 603601c

Browse files
authored
feat : 가입 요청 생성 및 조회 승인 및 거절 (#399)
1 parent 27de1b4 commit 603601c

File tree

14 files changed

+760
-7
lines changed

14 files changed

+760
-7
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.gamzabat.algohub.enums;
2+
3+
public enum JoinRequestStatus {
4+
PENDING("pending"),
5+
APPROVE("approve"),
6+
CANCEL("cancel"),
7+
REJECT("reject");
8+
private String value;
9+
10+
private JoinRequestStatus(String value) {
11+
this.value = value;
12+
}
13+
14+
public String getValue() {
15+
return value;
16+
}
17+
}

src/main/java/com/gamzabat/algohub/exception/CustomExceptionHandler.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.gamzabat.algohub.feature.group.studygroup.exception.CannotFoundUserException;
1818
import com.gamzabat.algohub.feature.group.studygroup.exception.GroupMemberValidationException;
1919
import com.gamzabat.algohub.feature.group.studygroup.exception.InvalidRoleException;
20+
import com.gamzabat.algohub.feature.group.studygroup.exception.JoinRequestException;
2021
import com.gamzabat.algohub.feature.image.exception.AwsS3Exception;
2122
import com.gamzabat.algohub.feature.notice.exception.NoticeValidationException;
2223
import com.gamzabat.algohub.feature.notification.exception.CannotFoundNotificationException;
@@ -233,7 +234,7 @@ protected ResponseEntity<ErrorResponse> handleCannotFoundVerificationCodeExcepti
233234
.status(HttpStatus.BAD_REQUEST)
234235
.body(new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage(), null));
235236
}
236-
237+
237238
@ExceptionHandler(CannotFoundEdgeCaseException.class)
238239
protected ResponseEntity<ErrorResponse> handleCannotFoundEdgeCaseException(
239240
CannotFoundEdgeCaseException e) {
@@ -249,5 +250,12 @@ protected ResponseEntity<ErrorResponse> handleNotAuthorizedUserException(
249250
.status(e.getHttpStatus())
250251
.body(new ErrorResponse(e.getHttpStatus().value(), e.getError(), null));
251252
}
252-
253+
254+
@ExceptionHandler(JoinRequestException.class)
255+
protected ResponseEntity<ErrorResponse> handleCannotFoundVerificationCodeException(
256+
JoinRequestException e) {
257+
return ResponseEntity
258+
.status(HttpStatus.BAD_REQUEST)
259+
.body(new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage(), null));
260+
}
253261
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.gamzabat.algohub.feature.group.studygroup.controller;
2+
3+
import java.util.List;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.validation.Errors;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.PathVariable;
9+
import org.springframework.web.bind.annotation.PostMapping;
10+
import org.springframework.web.bind.annotation.RequestBody;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
import com.gamzabat.algohub.common.annotation.AuthedUser;
15+
import com.gamzabat.algohub.feature.group.studygroup.domain.JoinRequest;
16+
import com.gamzabat.algohub.feature.group.studygroup.dto.UpdateJoinRequestStatusRequest;
17+
import com.gamzabat.algohub.feature.group.studygroup.exception.JoinRequestException;
18+
import com.gamzabat.algohub.feature.group.studygroup.service.JoinRequestService;
19+
import com.gamzabat.algohub.feature.user.domain.User;
20+
21+
import io.swagger.v3.oas.annotations.Operation;
22+
import io.swagger.v3.oas.annotations.tags.Tag;
23+
import jakarta.validation.Valid;
24+
import lombok.RequiredArgsConstructor;
25+
26+
@RestController
27+
@RequiredArgsConstructor
28+
@RequestMapping("/api")
29+
@Tag(name = "그룹 가입 요청API", description = "스터디 그룹 가입 요청 관련 API")
30+
public class JoinRequestController {
31+
private final JoinRequestService joinRequestService;
32+
33+
@PostMapping(value = "/groups/{groupId}/join-request")
34+
@Operation(summary = "그룹 가입 요청 API", description = "스터디 그룹에 가입 요청을 보내는 API")
35+
public ResponseEntity<Void> joinRequest(@AuthedUser User user, @PathVariable Long groupId) {
36+
joinRequestService.joinRequest(user, groupId);
37+
return ResponseEntity.ok().build();
38+
}
39+
40+
@GetMapping(value = "/groups/{groupId}/join-request")
41+
@Operation(summary = "그룹 가입 요청 목록 조회 API", description = "스터디 그룹 가입 요청 목록을 조회하는 API")
42+
public ResponseEntity<List<JoinRequest>> getAllJoinRequests(@AuthedUser User user, @PathVariable Long groupId) {
43+
List<JoinRequest> response = joinRequestService.getAllJoinRequests(user, groupId);
44+
45+
return ResponseEntity.ok().body(response);
46+
}
47+
48+
@PostMapping(value = "/join-request/{requestId}")
49+
@Operation(summary = "그룹 가입 요청 승인 / 거절", description = "스터디 그룹 가입 요청을 승인 / 거절하는 API")
50+
public ResponseEntity<Void> updateRequest(
51+
@AuthedUser User user,
52+
@PathVariable Long requestId,
53+
@RequestBody @Valid UpdateJoinRequestStatusRequest request, Errors errors) {
54+
if (errors.hasErrors())
55+
throw new JoinRequestException("가입 요청이 올바르지 않습니다.");
56+
joinRequestService.updateJoinRequest(user, requestId, request);
57+
return ResponseEntity.ok().build();
58+
}
59+
60+
}

src/main/java/com/gamzabat/algohub/feature/group/studygroup/controller/StudyGroupController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,5 @@ public ResponseEntity<Page<GetGroupResponse>> getSearchedGroupList(@RequestParam
208208
Page<GetGroupResponse> responses = studyGroupService.getSearchedStudyGroupList(searchPattern, pageable);
209209
return ResponseEntity.ok().body(responses);
210210
}
211+
211212
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.gamzabat.algohub.feature.group.studygroup.domain;
2+
3+
import com.gamzabat.algohub.enums.JoinRequestStatus;
4+
import com.gamzabat.algohub.feature.user.domain.User;
5+
6+
import jakarta.persistence.Entity;
7+
import jakarta.persistence.EnumType;
8+
import jakarta.persistence.Enumerated;
9+
import jakarta.persistence.FetchType;
10+
import jakarta.persistence.GeneratedValue;
11+
import jakarta.persistence.Id;
12+
import jakarta.persistence.JoinColumn;
13+
import jakarta.persistence.ManyToOne;
14+
import lombok.Getter;
15+
import lombok.NoArgsConstructor;
16+
17+
@Entity
18+
@Getter
19+
@NoArgsConstructor
20+
public class JoinRequest {
21+
@Id
22+
@GeneratedValue
23+
Long id;
24+
25+
@ManyToOne(fetch = FetchType.LAZY)
26+
@JoinColumn(name = "group_id")
27+
private StudyGroup group;
28+
29+
@ManyToOne(fetch = FetchType.LAZY)
30+
@JoinColumn(name = "user_id")
31+
private User requester;
32+
33+
@Enumerated(EnumType.STRING)
34+
private JoinRequestStatus status = JoinRequestStatus.PENDING;
35+
36+
public JoinRequest(StudyGroup group, User requester) {
37+
this.group = group;
38+
this.requester = requester;
39+
}
40+
41+
public void updateStatus(JoinRequestStatus status) {
42+
if (status != JoinRequestStatus.PENDING) {
43+
return;
44+
}
45+
this.status = status;
46+
}
47+
48+
}
49+
50+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.gamzabat.algohub.feature.group.studygroup.dto;
2+
3+
import com.gamzabat.algohub.enums.JoinRequestStatus;
4+
5+
import jakarta.validation.constraints.NotNull;
6+
7+
public record UpdateJoinRequestStatusRequest(@NotNull(message = "status 는 필수입니다.") JoinRequestStatus status) {
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.gamzabat.algohub.feature.group.studygroup.exception;
2+
3+
public class JoinRequestException extends RuntimeException {
4+
public JoinRequestException(String error) {
5+
super(error);
6+
}
7+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.gamzabat.algohub.feature.group.studygroup.repository;
2+
3+
import java.util.List;
4+
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
import com.gamzabat.algohub.feature.group.studygroup.domain.JoinRequest;
8+
9+
public interface JoinRequestRepository extends JpaRepository<JoinRequest, Long> {
10+
11+
boolean existsByGroup_IdAndRequester_Id(Long groupId, Long userId);
12+
13+
List<JoinRequest> findAllByGroup_Id(Long groupId);
14+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.gamzabat.algohub.feature.group.studygroup.service;
2+
3+
import java.time.LocalDate;
4+
import java.util.List;
5+
import java.util.Optional;
6+
7+
import org.springframework.http.HttpStatus;
8+
import org.springframework.stereotype.Service;
9+
import org.springframework.transaction.annotation.Transactional;
10+
11+
import com.gamzabat.algohub.enums.JoinRequestStatus;
12+
import com.gamzabat.algohub.exception.StudyGroupValidationException;
13+
import com.gamzabat.algohub.feature.group.ranking.domain.Ranking;
14+
import com.gamzabat.algohub.feature.group.ranking.repository.RankingRepository;
15+
import com.gamzabat.algohub.feature.group.studygroup.domain.GroupMember;
16+
import com.gamzabat.algohub.feature.group.studygroup.domain.JoinRequest;
17+
import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;
18+
import com.gamzabat.algohub.feature.group.studygroup.dto.UpdateJoinRequestStatusRequest;
19+
import com.gamzabat.algohub.feature.group.studygroup.etc.RoleOfGroupMember;
20+
import com.gamzabat.algohub.feature.group.studygroup.exception.GroupMemberValidationException;
21+
import com.gamzabat.algohub.feature.group.studygroup.exception.JoinRequestException;
22+
import com.gamzabat.algohub.feature.group.studygroup.repository.GroupMemberRepository;
23+
import com.gamzabat.algohub.feature.group.studygroup.repository.JoinRequestRepository;
24+
import com.gamzabat.algohub.feature.group.studygroup.repository.StudyGroupRepository;
25+
import com.gamzabat.algohub.feature.notification.domain.NotificationSetting;
26+
import com.gamzabat.algohub.feature.notification.repository.NotificationSettingRepository;
27+
import com.gamzabat.algohub.feature.user.domain.User;
28+
29+
import lombok.RequiredArgsConstructor;
30+
import lombok.extern.slf4j.Slf4j;
31+
32+
@Slf4j
33+
@Service
34+
@RequiredArgsConstructor
35+
public class JoinRequestService {
36+
private final StudyGroupRepository studyGroupRepository;
37+
private final GroupMemberRepository groupMemberRepository;
38+
private final JoinRequestRepository joinRequestRepository;
39+
private final NotificationSettingRepository notificationSettingRepository;
40+
private final RankingRepository rankingRepository;
41+
private final StudyGroupService studyGroupService;
42+
43+
@Transactional
44+
public void joinRequest(User user, Long groupId) {
45+
StudyGroup studyGroup = studyGroupRepository.findById(groupId)
46+
.orElseThrow(() -> new StudyGroupValidationException(HttpStatus.NOT_FOUND.value(), "존재하지 않는 그룹 입니다."));
47+
if (groupMemberRepository.existsByUserAndStudyGroup(user, studyGroup)) {
48+
throw new GroupMemberValidationException(HttpStatus.BAD_REQUEST.value(), "이미 가입한 그룹입니다");
49+
}
50+
if (joinRequestRepository.existsByGroup_IdAndRequester_Id(groupId, user.getId())) {
51+
throw new JoinRequestException("이미 요청한 그룹입니다.");
52+
}
53+
54+
JoinRequest request = new JoinRequest(studyGroup, user);
55+
joinRequestRepository.save(request);
56+
log.info("success to join request group = {}", groupId);
57+
}
58+
59+
@Transactional(readOnly = true)
60+
public List<JoinRequest> getAllJoinRequests(User user, Long groupId) {
61+
StudyGroup studyGroup = studyGroupRepository.findById(groupId)
62+
.orElseThrow(() -> new StudyGroupValidationException(HttpStatus.NOT_FOUND.value(), "존재하지 않는 그룹 입니다."));
63+
Optional<GroupMember> groupMember = groupMemberRepository.findByUserAndStudyGroup(user, studyGroup);
64+
if (groupMember.isPresent() && RoleOfGroupMember.isParticipant(groupMember.get()) || groupMember.isEmpty()) {
65+
throw new JoinRequestException("요청 목록을 조회할 권한이 없습니다.");
66+
}
67+
return joinRequestRepository.findAllByGroup_Id(groupId);
68+
}
69+
70+
@Transactional
71+
public void updateJoinRequest(User user, Long requestId, UpdateJoinRequestStatusRequest request) {
72+
JoinRequest joinRequest = joinRequestRepository.findById(requestId)
73+
.orElseThrow(() -> new JoinRequestException("해당 요청이 존재하지 않습니다"));
74+
StudyGroup studyGroup = studyGroupRepository.findById(joinRequest.getGroup().getId())
75+
.orElseThrow(() -> new StudyGroupValidationException(HttpStatus.NOT_FOUND.value(), "존재하지 않는 그룹입니다."));
76+
GroupMember groupMember = groupMemberRepository.findByUserAndStudyGroup(user, studyGroup)
77+
.orElseThrow(() -> new GroupMemberValidationException(HttpStatus.NOT_FOUND.value(), "해당 그룹의 멤버가 아닙니다."));
78+
if (RoleOfGroupMember.isParticipant(groupMember)) {
79+
throw new JoinRequestException("승인 권한이 없습니다.");
80+
}
81+
if (request.status() == JoinRequestStatus.APPROVE) {
82+
joinRequest.updateStatus(request.status());
83+
GroupMember newGroupMember = GroupMember.builder()
84+
.user(joinRequest.getRequester())
85+
.studyGroup(studyGroup)
86+
.joinDate(LocalDate.now())
87+
.role(RoleOfGroupMember.PARTICIPANT)
88+
.build();
89+
groupMemberRepository.save(newGroupMember);
90+
91+
notificationSettingRepository.save(
92+
NotificationSetting.builder().member(newGroupMember).build()
93+
);
94+
95+
rankingRepository.save(Ranking.builder()
96+
.member(newGroupMember)
97+
.currentRank(groupMemberRepository.countByStudyGroup(studyGroup))
98+
.solvedCount(0)
99+
.rankDiff("-")
100+
.build()
101+
);
102+
studyGroupService.sendNewMemberNotification(studyGroup, newGroupMember);
103+
104+
joinRequestRepository.delete(joinRequest);
105+
} else if (request.status() == JoinRequestStatus.REJECT) {
106+
joinRequestRepository.delete(joinRequest);
107+
}
108+
log.info("success to approve/reject for join request group = {}", studyGroup.getId());
109+
}
110+
111+
}

src/main/java/com/gamzabat/algohub/feature/group/studygroup/service/StudyGroupService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.gamzabat.algohub.feature.group.studygroup.exception.GroupMemberValidationException;
5353
import com.gamzabat.algohub.feature.group.studygroup.repository.BookmarkedStudyGroupRepository;
5454
import com.gamzabat.algohub.feature.group.studygroup.repository.GroupMemberRepository;
55+
import com.gamzabat.algohub.feature.group.studygroup.repository.JoinRequestRepository;
5556
import com.gamzabat.algohub.feature.group.studygroup.repository.StudyGroupRepository;
5657
import com.gamzabat.algohub.feature.image.service.ImageService;
5758
import com.gamzabat.algohub.feature.notice.repository.NoticeCommentRepository;
@@ -87,6 +88,7 @@ public class StudyGroupService {
8788
private final RankingRepository rankingRepository;
8889
private final NoticeRepository noticeRepository;
8990
private final NoticeCommentRepository noticeCommentRepository;
91+
private final JoinRequestRepository joinRequestRepository;
9092
private final SolutionCommentRepository solutionCommentRepository;
9193
private final NoticeReadRepository noticeReadRepository;
9294
private final ObjectProvider<StudyGroupService> studyGroupServiceProvider;
@@ -609,7 +611,7 @@ private boolean isVisible(StudyGroup group, User user) {
609611
return groupMemberRepository.existsByUserAndStudyGroupAndIsVisible(user, group, true);
610612
}
611613

612-
private void sendNewMemberNotification(StudyGroup studyGroup, GroupMember newMember) {
614+
void sendNewMemberNotification(StudyGroup studyGroup, GroupMember newMember) {
613615
List<GroupMember> members = groupMemberRepository.findAllByStudyGroup(studyGroup)
614616
.stream()
615617
.filter(member -> !member.getId().equals(newMember.getId()))
@@ -657,4 +659,4 @@ public Page<GetGroupResponse> getSearchedStudyGroupList(String searchPattern, Pa
657659
));
658660
}
659661

660-
}
662+
}

0 commit comments

Comments
 (0)