Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 44 additions & 25 deletions src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import org.springframework.web.bind.annotation.RequestMapping;

import gg.agit.konect.domain.club.dto.ClubApplicationAnswersResponse;
import gg.agit.konect.domain.club.dto.ClubAppliedClubsResponse;
import gg.agit.konect.domain.club.dto.ClubApplicationsResponse;
import gg.agit.konect.domain.club.dto.ClubAppliedClubsResponse;
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsResponse;
import gg.agit.konect.domain.club.dto.ClubApplyRequest;
Expand Down Expand Up @@ -77,7 +77,7 @@ ResponseEntity<ClubDetailResponse> getClubDetail(

@Operation(summary = "새로운 동아리를 생성한다.", description = """
새로운 동아리를 생성하고, 생성한 사용자를 회장으로 등록합니다.

## 에러
- NOT_FOUND_USER (404): 유저를 찾을 수 없습니다.
""")
Expand All @@ -91,7 +91,7 @@ ResponseEntity<ClubDetailResponse> createClub(
동아리 회장 또는 부회장만 동아리 프로필을 수정할 수 있습니다.
수정 가능 항목: 한 줄 소개, 로고 이미지, 태그
동아리명과 분과는 수정할 수 없으며, 변경이 필요한 경우 문의하기를 통해 어드민에게 요청하세요.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
Expand All @@ -107,7 +107,7 @@ ResponseEntity<Void> updateProfile(
@Operation(summary = "동아리 상세정보를 수정한다.", description = """
동아리 회장 또는 부회장만 동아리 상세정보를 수정할 수 있습니다.
수정 가능 항목: 동방 위치, 상세 소개

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
Expand All @@ -124,7 +124,7 @@ ResponseEntity<Void> updateDetails(
어드민만 동아리 기본정보를 수정할 수 있습니다.
수정 가능 항목: 동아리명, 분과
일반 관리자는 이 API를 사용할 수 없으며, 변경이 필요한 경우 문의하기를 통해 어드민에게 요청하세요.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 어드민 권한이 없습니다.
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
Expand Down Expand Up @@ -163,7 +163,7 @@ ResponseEntity<ClubAppliedClubsResponse> getAppliedClubs(
- 동아리 관리자만 해당 동아리의 지원 내역을 조회할 수 있습니다.
- 현재 지정된 모집 일정 범위에 지원한 내역만 볼 수 있습니다.
- 상시 모집의 경우 모든 내역을 봅니다.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
Expand All @@ -177,7 +177,7 @@ ResponseEntity<ClubApplicationsResponse> getClubApplications(

@Operation(summary = "동아리 지원 답변을 조회한다.", description = """
- 동아리 관리자만 해당 동아리의 지원 답변을 조회할 수 있습니다.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
Expand All @@ -190,10 +190,29 @@ ResponseEntity<ClubApplicationAnswersResponse> getClubApplicationAnswers(
@UserId Integer userId
);

@Operation(summary = "동아리 가입 신청을 승인한다.", description = """
동아리 회장 또는 부회장만 가입 신청을 승인할 수 있습니다.
승인 시 지원자는 동아리 회원으로 등록되고, 지원 내역은 삭제됩니다.
승인 시 회비는 지원 절차에서 이미 낸 것으로 간주됩니다.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
- NOT_FOUND_CLUB_APPLY (404): 동아리 지원 내역을 찾을 수 없습니다.
- NOT_FOUND_CLUB_POSITION (404): 동아리 직책을 찾을 수 없습니다.
- ALREADY_CLUB_MEMBER (409): 이미 동아리 회원입니다.
""")
@PostMapping("/{clubId}/applications/{applicationId}/approve")
ResponseEntity<Void> approveClubApplication(
@PathVariable(name = "clubId") Integer clubId,
@PathVariable(name = "applicationId") Integer applicationId,
@UserId Integer userId
);

@Operation(summary = "동아리 멤버 리스트를 조회한다.", description = """
동아리 회원만 멤버 리스트를 조회할 수 있습니다.
positionGroup 파라미터로 특정 직책 그룹의 회원만 필터링할 수 있습니다.

## 에러
- FORBIDDEN_CLUB_MEMBER_ACCESS (403): 동아리 멤버 조회 권한이 없습니다.
""")
Expand All @@ -207,7 +226,7 @@ ResponseEntity<ClubMembersResponse> getClubMembers(
@Operation(summary = "동아리 가입 신청을 한다.", description = """
동아리 가입 신청서를 제출합니다.
설문 질문이 없는 경우 answers는 빈 배열을 전달합니다.

- ALREADY_APPLIED_CLUB (409): 이미 가입 신청을 완료한 사용자입니다.
- NOT_FOUND_CLUB_APPLY_QUESTION (404): 존재하지 않는 가입 문항입니다.
- DUPLICATE_CLUB_APPLY_QUESTION (409): 중복된 id의 가입 문항이 포함되어 있습니다.
Expand All @@ -222,7 +241,7 @@ ResponseEntity<ClubFeeInfoResponse> applyClub(

@Operation(summary = "동아리 회비 정보를 조회한다.", description = """
동아리 가입 신청을 완료했거나 동아리 관리자 권한이 있는 사용자만 회비 계좌 정보를 조회할 수 있습니다.

## 에러
- FORBIDDEN_CLUB_FEE_INFO (403): 회비 정보 조회 권한이 없습니다.
""")
Expand All @@ -237,7 +256,7 @@ ResponseEntity<ClubFeeInfoResponse> getFeeInfo(
- 모든 필드를 전달하면 생성/수정합니다.
- 모든 필드가 null이면 회비 정보를 삭제합니다.
- 일부 필드가 누락된 경우 에러가 발생합니다.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- INVALID_REQUEST_BODY (400): 요청 본문의 형식이 올바르지 않거나 필수 값이 누락된 경우
Expand All @@ -262,7 +281,7 @@ ResponseEntity<ClubApplyQuestionsResponse> getApplyQuestions(
- questionId가 없으면 생성
- 요청에 없는 기존 문항은 삭제됩니다.
- 저장된 문항 목록을 반환합니다.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB_APPLY_QUESTION (404): 존재하지 않는 가입 문항입니다.
Expand All @@ -277,10 +296,10 @@ ResponseEntity<ClubApplyQuestionsResponse> replaceApplyQuestions(

@Operation(summary = "동아리 모집 정보를 조회한다.", description = """
동아리의 모집 공고 상세 정보를 조회합니다.

- status는 모집 기간에 따라 BEFORE(모집 전), ONGOING(모집 중), CLOSED(모집 마감)으로 반환됩니다.
- 동아리 멤버이거나 지원 이력이 존재할 경우 isApplied는 true로 반환됩니다.

## 에러
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
- NOT_FOUND_USER (404): 유저를 찾을 수 없습니다.
Expand All @@ -295,7 +314,7 @@ ResponseEntity<ClubRecruitmentResponse> getRecruitments(
@Operation(summary = "동아리 모집 정보를 생성한다.", description = """
동아리 회장만 모집 공고를 생성할 수 있습니다.
한 동아리당 하나의 모집 공고만 생성 가능합니다.

## 에러
- INVALID_RECRUITMENT_DATE_NOT_ALLOWED (400): 상시 모집인 경우 모집 시작일과 마감일을 지정할 수 없습니다.
- INVALID_RECRUITMENT_DATE_REQUIRED (400): 상시 모집이 아닐 경우 모집 시작일과 마감일이 필수입니다.
Expand All @@ -314,7 +333,7 @@ ResponseEntity<Void> createRecruitment(

@Operation(summary = "동아리 모집 정보를 수정한다.", description = """
동아리 회장 또는 부회장만 모집 공고를 수정할 수 있습니다.

## 에러
- INVALID_RECRUITMENT_DATE_NOT_ALLOWED (400): 상시 모집인 경우 모집 시작일과 마감일을 지정할 수 없습니다.
- INVALID_RECRUITMENT_DATE_REQUIRED (400): 상시 모집이 아닐 경우 모집 시작일과 마감일이 필수입니다.
Expand All @@ -334,7 +353,7 @@ ResponseEntity<Void> updateRecruitment(
@Operation(summary = "동아리 직책 목록을 조회한다.", description = """
동아리의 모든 직책을 우선순위 순으로 조회합니다.
각 직책의 회원 수, 수정/삭제 가능 여부도 함께 반환됩니다.

## 에러
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
""")
Expand All @@ -347,7 +366,7 @@ ResponseEntity<ClubPositionsResponse> getClubPositions(
@Operation(summary = "동아리 직책을 생성한다.", description = """
동아리 회장 또는 부회장만 직책을 생성할 수 있습니다.
PRESIDENT와 VICE_PRESIDENT 직책은 생성할 수 없으며, MANAGER 또는 MEMBER 그룹의 직책만 생성 가능합니다.

## 에러
- POSITION_NAME_DUPLICATED (400): 동일한 직책 이름이 이미 존재합니다.
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
Expand All @@ -363,7 +382,7 @@ ResponseEntity<ClubPositionsResponse> createClubPosition(
@Operation(summary = "동아리 직책의 이름을 수정한다.", description = """
동아리 회장 또는 부회장만 직책 이름을 수정할 수 있습니다.
PRESIDENT와 VICE_PRESIDENT 직책의 이름은 변경할 수 없습니다.

## 에러
- POSITION_NAME_DUPLICATED (400): 동일한 직책 이름이 이미 존재합니다.
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
Expand All @@ -383,7 +402,7 @@ ResponseEntity<ClubPositionsResponse> updateClubPositionName(
동아리 회장 또는 부회장만 직책을 삭제할 수 있습니다.
PRESIDENT와 VICE_PRESIDENT 직책은 삭제할 수 없습니다.
해당 직책을 사용 중인 회원이 없어야 하며, 해당 그룹에 최소 2개의 직책이 있어야 삭제 가능합니다.

## 에러
- CANNOT_DELETE_ESSENTIAL_POSITION (400): 필수 직책은 삭제할 수 없습니다.
- POSITION_IN_USE (400): 해당 직책을 사용 중인 회원이 있어 삭제할 수 없습니다.
Expand All @@ -402,7 +421,7 @@ ResponseEntity<Void> deleteClubPosition(
@Operation(summary = "동아리 회원의 직책을 변경한다.", description = """
동아리 회장 또는 부회장만 회원의 직책을 변경할 수 있습니다.
자기 자신의 직책은 변경할 수 없으며, 상위 직급만 하위 직급의 회원을 관리할 수 있습니다.

## 에러
- CANNOT_CHANGE_OWN_POSITION (400): 자기 자신의 직책은 변경할 수 없습니다.
- CANNOT_MANAGE_HIGHER_POSITION (400): 자신보다 높은 직급의 회원은 관리할 수 없습니다.
Expand All @@ -424,7 +443,7 @@ ResponseEntity<Void> changeMemberPosition(
@Operation(summary = "동아리 회장 권한을 위임한다.", description = """
현재 회장만 회장 권한을 다른 회원에게 위임할 수 있습니다.
회장 위임 시 현재 회장은 일반회원으로 강등됩니다.

## 에러
- ILLEGAL_ARGUMENT (400): 자기 자신에게는 위임할 수 없습니다.
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 회장 권한이 없습니다.
Expand All @@ -443,7 +462,7 @@ ResponseEntity<Void> transferPresident(
@Operation(summary = "동아리 부회장을 변경한다.", description = """
동아리 회장만 부회장을 임명하거나 해제할 수 있습니다.
vicePresidentUserId가 null이면 부회장을 해제하고, 값이 있으면 해당 회원을 부회장으로 임명합니다.

## 에러
- CANNOT_CHANGE_OWN_POSITION (400): 자기 자신을 부회장으로 임명할 수 없습니다.
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 회장 권한이 없습니다.
Expand All @@ -461,7 +480,7 @@ ResponseEntity<Void> changeVicePresident(
@Operation(summary = "동아리에 회원을 직접 추가한다.", description = """
동아리 회장 또는 부회장만 회원을 직접 추가할 수 있습니다.
회장 직책으로는 추가할 수 없으며, 부회장과 운영진은 인원 제한이 있습니다.

## 에러
- ALREADY_CLUB_MEMBER (409): 이미 동아리 회원입니다.
- VICE_PRESIDENT_ALREADY_EXISTS (409): 부회장은 이미 존재합니다.
Expand All @@ -481,7 +500,7 @@ ResponseEntity<Void> addMember(
@Operation(summary = "동아리 회원을 강제 탈퇴시킨다.", description = """
동아리 회장 또는 부회장만 회원을 강제 탈퇴시킬 수 있습니다.
일반회원만 강제 탈퇴 가능하며, 부회장이나 운영진은 먼저 직책을 변경한 후 탈퇴시켜야 합니다.

## 에러
- CANNOT_REMOVE_SELF (400): 자기 자신을 강제 탈퇴시킬 수 없습니다.
- CANNOT_REMOVE_NON_MEMBER (400): 일반회원만 강제 탈퇴할 수 있습니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import org.springframework.web.bind.annotation.RestController;

import gg.agit.konect.domain.club.dto.ClubApplicationAnswersResponse;
import gg.agit.konect.domain.club.dto.ClubAppliedClubsResponse;
import gg.agit.konect.domain.club.dto.ClubApplicationsResponse;
import gg.agit.konect.domain.club.dto.ClubAppliedClubsResponse;
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsResponse;
import gg.agit.konect.domain.club.dto.ClubApplyRequest;
Expand Down Expand Up @@ -162,6 +162,16 @@ public ResponseEntity<ClubApplicationAnswersResponse> getClubApplicationAnswers(
return ResponseEntity.ok(response);
}

@Override
public ResponseEntity<Void> approveClubApplication(
@PathVariable(name = "clubId") Integer clubId,
@PathVariable(name = "applicationId") Integer applicationId,
@UserId Integer userId
) {
clubService.approveClubApplication(clubId, applicationId, userId);
return ResponseEntity.ok().build();
}

@Override
public ResponseEntity<ClubMembersResponse> getClubMembers(
@PathVariable(name = "clubId") Integer clubId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public record ClubCreateRequest(
@Size(max = 50, message = "동아리 이름은 50자 이하여야 합니다.")
String name,

@Schema(description = "동아리 한 줄 소개", example = "즐겁게 일하고 열심히 노는 IT 특성화 동아리",
@Schema(description = "동아리 한 줄 소개", example = "즐겁게 일하고 노는 IT 동아리",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 소개는 필수 입력입니다.")
@Size(max = 20, message = "동아리 소개는 20자 이하여야 합니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public record ClubUpdateRequest(
@Size(max = 50, message = "동아리 이름은 50자 이하여야 합니다.")
String name,

@Schema(description = "동아리 한 줄 소개", example = "즐겁게 일하고 열심히 노는 IT 특성화 동아리",
@Schema(description = "동아리 한 줄 소개", example = "즐겁게 일하고 노는 IT 동아리",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 소개는 필수 입력입니다.")
@Size(max = 20, message = "동아리 소개는 20자 이하여야 합니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ List<Integer> findClubIdsByUserIdAndClubIdIn(

ClubApply save(ClubApply clubApply);

void delete(ClubApply clubApply);

void deleteByUserId(Integer userId);

@Query("""
Expand Down
33 changes: 31 additions & 2 deletions src/main/java/gg/agit/konect/domain/club/service/ClubService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package gg.agit.konect.domain.club.service;

import static gg.agit.konect.domain.club.enums.ClubPositionGroup.MANAGER;
import static gg.agit.konect.domain.club.enums.ClubPositionGroup.PRESIDENT;
import static gg.agit.konect.domain.club.enums.ClubPositionGroup.*;
import static gg.agit.konect.global.code.ApiResponseCode.*;

import java.time.LocalDateTime;
Expand Down Expand Up @@ -82,6 +81,8 @@ public class ClubService {
EnumSet.of(PRESIDENT);
private static final Set<ClubPositionGroup> MANAGER_ALLOWED_GROUPS =
EnumSet.of(PRESIDENT, MANAGER);
private static final Set<ClubPositionGroup> LEADER_ALLOWED_GROUPS =
EnumSet.of(PRESIDENT, VICE_PRESIDENT);

private final ClubQueryRepository clubQueryRepository;
private final ClubRepository clubRepository;
Expand Down Expand Up @@ -278,6 +279,34 @@ public ClubApplicationAnswersResponse getClubApplicationAnswers(
return ClubApplicationAnswersResponse.of(clubApply, questions, answers);
}

@Transactional
public void approveClubApplication(Integer clubId, Integer applicationId, Integer userId) {
Club club = clubRepository.getById(clubId);

if (!hasClubManageAccess(clubId, userId, LEADER_ALLOWED_GROUPS)) {
throw CustomException.of(FORBIDDEN_CLUB_MANAGER_ACCESS);
}

ClubApply clubApply = clubApplyRepository.getByIdAndClubId(applicationId, clubId);
User applicant = clubApply.getUser();

if (clubMemberRepository.existsByClubIdAndUserId(clubId, applicant.getId())) {
throw CustomException.of(ALREADY_CLUB_MEMBER);
}

ClubPosition memberPosition = clubPositionRepository.getFirstByClubIdAndClubPositionGroup(clubId, MEMBER);

ClubMember newMember = ClubMember.builder()
.club(club)
.user(applicant)
.clubPosition(memberPosition)
.isFeePaid(true)
.build();

clubMemberRepository.save(newMember);
clubApplyRepository.delete(clubApply);
}

private List<ClubApply> findApplicationsByRecruitmentPeriod(
Integer clubId,
ClubRecruitment recruitment
Expand Down