Skip to content

Conversation

@JanooGwan
Copy link
Contributor

동아리 내 권한 관리 기능 구현 완료했습니다.

'부회장' 권한(ClubPositionGroup) 추가하였고,
동아리 생성 시 '회장', '부회장', '운영진', '일반회원' 각각 한 개씩(ClubPosition) 기본 생성되도록 하였습니다.

또한 동아리 내 권한을 관리하거나, 동아리 내 회원 종류(ClubPosition)를 새로 만들거나 변경할 수 있도록 하였습니다.

동아리 권한 변경 관련하여 어떤 방식으로 구현했는지는 하단에 서술하도록 하겠습니다.


  1. ClubPositionGroup 종류
  • PRESIDENT ("회장")
  • VICE_PRESIDENT ("부회장") // 새로 추가
  • MANAGER ("운영진")
  • MEMBER ("일반회원")
  1. 인원 제한
  • 회장: 정확히 1명
  • 부회장: 0~1명 (초기 생성 시에는 0명)
  • 운영진(MANAGER 그룹 전체): 0~20명
  • 일반회원: 제한없음
  1. 권한 계층
  • 회장 > 부회장 > 운영진 > 일반회원
  • 상위 직급만 하위 직급 관리 가능
  • 자기 자신 작업 불가
  • 회장 본인 탈퇴 불가 (위임 필요)
  1. 회장/부회장 관리
  • 회장 위임: 다른 회원에게 위임 → 기존 회장 자동으로 일반회원
  • 부회장 변경: 새 부회장 지정 → 기존 부회장 자동으로 일반회원
  1. ClubPosition 관리 권한
  • 회장, 부회장만 가능
  • MANAGER(운영진)/MEMBER(일반회원) 에 대한 새로운 회원종류(ClubPosition) 생성 가능
  • 이름 중복 불가
  • positionGroup 변경 불가
  1. ClubPosition 수정/삭제 규칙
  • 회장(PRESIDENT)과 부회장(VICE_PRESIDENT)은 이름을 변경할 수 없고, 삭제도 불가능함
  • 운영진(MANAGER)과 일반회원(MEMBER)에 해당하는 ClubPosition은 그 이름을 변경할 수 있고, 삭제도 가능함. 단 삭제 시에는 특정 조건(그룹 내 인원이 없고, 삭제하고자 하는 그룹이 1개만 있는게 아닌 경우)을 만족해야함
  1. 동아리 생성
  • 생성 시, 생성한 사용자로 회장 자동 설정
  • 기본 4개 직책 자동 생성
  • 부회장은 일단 null로 기본값
  1. API 구현 범위
  • 회원 직급 변경
  • 회장 위임
  • 회원 강제 탈퇴
  • 부원 명부 조회 (전체 + 직급별)
  • ClubPosition CRUD
  1. 정렬
  • 회장 → 부회장 → 운영진 → 일반회원
  • 같은 그룹 내 이름순

@JanooGwan JanooGwan force-pushed the feat/CAM-175-update-club-member-authority branch from a7ad764 to a01dc72 Compare January 19, 2026 12:14
Copy link
Contributor

@dh2906 dh2906 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다.
웬만해서는 하나의 API당 하나의 PR로 작업을 올려주세요! 작업의 볼륨이 커진다면 더 쪼개주시면 좋고요.
그리고 개발한 API는 테스트 후 PR 올리시는 지 궁금합니다!

Comment on lines +25 to +26
@Schema(description = "우선순위 (낮을수록 높은 직급)", example = "0", requiredMode = REQUIRED)
Integer priority,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선순위를 넣은 이유가 무엇인가요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선순위를 이용해서 직급 간 관리를 수행할 수 있도록 하려고 했습니다
(ex. 회장은 운영진보다 우선순위가 높음 -> 회장은 운영진 관리 가능, 그 반대는 못함)
하지만 권한 종류가 4개(회장, 부회장, 운영진, 일반)로 많지 않기 때문에,
priority 필드를 지우고, repository에서 다음과 같이 수정하려고 하고 있습니다.

@Query("""
        SELECT cp
        FROM ClubPosition cp
        WHERE cp.club.id = :clubId
        ORDER BY 
            CASE cp.clubPositionGroup
                WHEN gg.agit.konect.domain.club.enums.ClubPositionGroup.PRESIDENT THEN 0
                WHEN gg.agit.konect.domain.club.enums.ClubPositionGroup.VICE_PRESIDENT THEN 1
                WHEN gg.agit.konect.domain.club.enums.ClubPositionGroup.MANAGER THEN 2
                WHEN gg.agit.konect.domain.club.enums.ClubPositionGroup.MEMBER THEN 3
            END ASC,
            cp.name ASC
        """)
    List<ClubPosition> findAllByClubId(@Param("clubId") Integer clubId);

Comment on lines +31 to +35
@Schema(description = "이름 변경 가능 여부", example = "false", requiredMode = REQUIRED)
Boolean canRename,

@Schema(description = "삭제 가능 여부", example = "false", requiredMode = REQUIRED)
Boolean canDelete
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런건 너무 과한 것 같아 보입니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필드로 꼭 추가할 필요는 없다는 말씀이신가요??
다시 보니 굳이인가 싶기도 하긴 합니다
필드 없이 비즈니스 로직 만으로도 충분할 것 같습니다

Comment on lines +291 to +297
@PatchMapping("/{clubId}/positions/{positionId}")
ResponseEntity<ClubPositionsResponse> updateClubPositionName(
@PathVariable(name = "clubId") Integer clubId,
@PathVariable(name = "positionId") Integer positionId,
@Valid @RequestBody ClubPositionUpdateRequest request,
@UserId Integer userId
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. PATCH로 지정한 이유가 따로 있나요?
  2. positionId를 요청 바디에서 받는 형태는 어떻게 생각하시나요? 다른 API에서도 마찬가지 입니당

Copy link
Contributor Author

@JanooGwan JanooGwan Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. ClubPositionname만 변경하게 되는 것이라 PATCH 로 일부 필드만 변경되게끔 하는게 적절하다고 생각했습니다.
  2. 그렇게 하는게 더 좋을 것 같습니다. uri 길이도 짧아지면서 깔끔해질 것 같습니다
    (수정) 좀더 생각해봤는데 지금처럼 두는게 좋다고 생각합니다.
    그 이유는 ClubPosition 내에서 변경 가능한 필드는 name 밖에 없어서,
    여러 필드를 수정하지 않고 name 필드만 수정하기 때문에 지금처럼 uri 설계하고 매개변수에 positionId 를 두는 것이 적절하다고 생각했습니다.

Comment on lines +300 to +302
동아리 회장 또는 매니저만 직책을 삭제할 수 있습니다.
PRESIDENT와 VICE_PRESIDENT 직책은 삭제할 수 없습니다.
해당 직책을 사용 중인 회원이 없어야 하며, 해당 그룹에 최소 2개의 직책이 있어야 삭제 가능합니다.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

직책 삭제의 경우 회장, 부회장만 가능하도록 하는 것이 좋아 보여요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알겠습니다 매니저는 직책 삭제 못하도록 하겠습니다!

Comment on lines +320 to +321
동아리 회장 또는 매니저만 회원의 직책을 변경할 수 있습니다.
자기 자신의 직책은 변경할 수 없으며, 상위 직급만 하위 직급의 회원을 관리할 수 있습니다.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어차피 회장, 부회장은 직급과 이름이 고정되어 있기에 여기도 마찬가지로 회장, 부회장만 변경 가능하도록 해야할 것 같습니당.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다

@Operation(summary = "동아리 회원을 강제 탈퇴시킨다.", description = """
동아리 회장 또는 매니저만 회원을 강제 탈퇴시킬 수 있습니다.
자기 자신은 강제 탈퇴시킬 수 없으며, 회장은 강제 탈퇴시킬 수 없습니다.
상위 직급만 하위 직급의 회원을 강제 탈퇴시킬 수 있습니다.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 상위, 하위 직급을 나눈다면 발생할 수 있는 케이스가 복잡해지니 일반 멤버에 대해서만 탈퇴를 시키는건 어떻게 생각하시나요?

만약 임원진이나 부회장을 탈퇴시켜야 한다면 회장의 권한으로 직급을 일반 멤버로 내린 뒤에 탈퇴하면 되니까 더 간단해질 것으로 보여요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알겠습니다
말씀해주신 대로 탈퇴는 일반 멤버에 대해서만 가능하도록 하겠습니다

Comment on lines 47 to 59
if (targetUserId.equals(requesterId)) {
throw CustomException.of(CANNOT_CHANGE_OWN_POSITION);
}

ClubMember requester = clubMemberRepository.findByClubIdAndUserId(clubId, requesterId)
.orElseThrow(() -> CustomException.of(NOT_FOUND_CLUB_MEMBER));

ClubMember target = clubMemberRepository.findByClubIdAndUserId(clubId, targetUserId)
.orElseThrow(() -> CustomException.of(NOT_FOUND_CLUB_MEMBER));

if (!requester.canManage(target)) {
throw CustomException.of(CANNOT_MANAGE_HIGHER_POSITION);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if문은 따로 private 메소드로 분리하는건 어떠세요?

repository에서 조회한 엔티티도 검증하는 로직을 getBy~~~처럼 인터페이스의 default 메소드를 만들어 활용하는건 어떠신가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 이거 전에도 말씀해 주셨던거 같은데 수정하도록 하겠습니다..

Comment on lines +118 to +137
ClubPosition.builder()
.name("회장")
.clubPositionGroup(PRESIDENT)
.club(savedClub)
.build(),
ClubPosition.builder()
.name("부회장")
.clubPositionGroup(VICE_PRESIDENT)
.club(savedClub)
.build(),
ClubPosition.builder()
.name("운영진")
.clubPositionGroup(MANAGER)
.club(savedClub)
.build(),
ClubPosition.builder()
.name("일반회원")
.clubPositionGroup(MEMBER)
.club(savedClub)
.build()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for-each? 였는지 기억은 잘 안나지만 enum에 있는 내용을 반복할 수 있는 방법이 있는 것으로 알고 있습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 그런가요? 조사해보고 수정하도록 하겠습니다!

@JanooGwan JanooGwan changed the title 동아리 내 권한 관리 기능 구현 feat: 동아리 내 권한 관리 기능 구현 Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants