Skip to content

Commit 700e7b8

Browse files
authored
[feat/#496] 멤버 역할(부모임장) 설정 API 구현 (#497)
* feat: 멤버 역할(부모임장) 설정 API 구현 * feat: 멤버 역할(부모임장) 설정 API 서비스 구현체 * feat: 멤버 역할(부모임장) 설정 API 알림 발송 구현 * feat: 멤버 역할(부모임장) 설정 API 검증 및 권한 로직 추가 * fix: 같은 역할일 경우, 에러 처리가 아닌 return 해주도록 수정
1 parent b68ba6d commit 700e7b8

13 files changed

Lines changed: 160 additions & 11 deletions

File tree

‎src/main/java/umc/cockple/demo/domain/exercise/service/ExerciseValidator.java‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class ExerciseValidator {
3232

3333
public void validateCreateExercise(Long memberId, ExerciseCreateDTO.Request request, Party party) {
3434
validatePartyIsActive(party);
35-
validateMemberPermission(memberId, party);
35+
validateSubManagerPermission(memberId, party);
3636
validateExerciseTime(request);
3737
}
3838

@@ -62,19 +62,19 @@ public void validateCancelGuestInvitation(Exercise exercise, Guest guest, Member
6262

6363
public void validateCancelCommonParticipationByManager(Exercise exercise, Member manager) {
6464
validateAlreadyStarted(exercise, ExerciseErrorCode.EXERCISE_ALREADY_STARTED_CANCEL);
65-
validateMemberPermission(manager.getId(), exercise.getParty());
65+
validateSubManagerPermission(manager.getId(), exercise.getParty());
6666
}
6767

6868
public void validateCancelGuestParticipationByManager(Guest guest, Exercise exercise){
6969
validateGuestBelongsToExercise(guest, exercise);
7070
}
7171

7272
public void validateDeleteExercise(Exercise exercise, Long memberId) {
73-
validateMemberPermission(memberId, exercise.getParty());
73+
validateManagerPermission(memberId, exercise.getParty());
7474
}
7575

7676
public void validateUpdateExercise(Exercise exercise, Member member, ExerciseUpdateDTO.Request request) {
77-
validateMemberPermission(member.getId(), exercise.getParty());
77+
validateManagerPermission(member.getId(), exercise.getParty());
7878
validateAlreadyStarted(exercise, ExerciseErrorCode.EXERCISE_ALREADY_STARTED_UPDATE);
7979
validateUpdateTime(request, exercise);
8080
}
@@ -87,7 +87,7 @@ private void validatePartyIsActive(Party party) {
8787
}
8888
}
8989

90-
private void validateMemberPermission(Long memberId, Party party) {
90+
private void validateManagerPermission(Long memberId, Party party) {
9191
boolean isOwner = party.getOwnerId().equals(memberId);
9292
boolean isManager = memberPartyRepository.existsByPartyIdAndMemberIdAndRole(
9393
party.getId(), memberId, Role.party_MANAGER);
@@ -96,6 +96,17 @@ private void validateMemberPermission(Long memberId, Party party) {
9696
throw new ExerciseException(ExerciseErrorCode.INSUFFICIENT_PERMISSION);
9797
}
9898

99+
private void validateSubManagerPermission(Long memberId, Party party) {
100+
boolean isOwner = party.getOwnerId().equals(memberId);
101+
boolean isManager = memberPartyRepository.existsByPartyIdAndMemberIdAndRole(
102+
party.getId(), memberId, Role.party_MANAGER);
103+
boolean isSubManager = memberPartyRepository.existsByPartyIdAndMemberIdAndRole(
104+
party.getId(), memberId, Role.party_SUBMANAGER);
105+
106+
if (!isOwner && !isManager && !isSubManager)
107+
throw new ExerciseException(ExerciseErrorCode.INSUFFICIENT_PERMISSION);
108+
}
109+
99110
private void validateExerciseTime(ExerciseCreateDTO.Request request) {
100111
LocalDate date = request.toParsedDate();
101112
LocalTime startTime = request.toParsedStartTime();

‎src/main/java/umc/cockple/demo/domain/member/domain/MemberParty.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,8 @@ public boolean isLeader() {
6767
if (this.role == Role.party_MANAGER) return true;
6868
return false;
6969
}
70+
71+
public void changeRole(Role newRole) {
72+
this.role = newRole;
73+
}
7074
}

‎src/main/java/umc/cockple/demo/domain/member/exception/MemberErrorCode.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public enum MemberErrorCode implements BaseErrorCode {
2323
ALREADY_WITHDRAW(HttpStatus.UNAUTHORIZED, "MEMBER301", "이미 탈퇴한 회원입니다."),
2424

2525
MANAGER_CANNOT_LEAVE(HttpStatus.BAD_REQUEST, "MEMBER401", "모임장은 탈퇴할 수 없습니다. 모임 삭제를 먼저 해주세요."),
26+
SUBMANAGER_CANNOT_LEAVE(HttpStatus.BAD_REQUEST, "MEMBER402", "부모임장은 탈퇴할 수 없습니다. 모임 탈퇴를 먼저 해주세요."),
2627

2728
// 회원 주소 관련
2829
ADDRESS_NOT_FOUND(HttpStatus.NOT_FOUND, "MEM_ADDR201", "해당 주소를 찾을 수 없습니다."),

‎src/main/java/umc/cockple/demo/domain/member/repository/MemberPartyRepository.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,6 @@ List<Long> findAllPartyIdsByMemberAndPartyIds(@Param("memberId") Long memberId,
5454
WHERE mp.party.id = :partyId
5555
""")
5656
List<MemberParty> findAllByPartyIdWithMember(@Param("partyId") Long partyId);
57+
58+
Optional<MemberParty> findByPartyIdAndRole(Long partyId, Role role);
5759
}

‎src/main/java/umc/cockple/demo/domain/member/service/MemberCommandService.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import umc.cockple.demo.domain.member.repository.*;
1717
import umc.cockple.demo.domain.member.enums.MemberStatus;
1818
import umc.cockple.demo.domain.image.service.ImageService;
19+
import umc.cockple.demo.global.enums.Role;
1920

2021
import java.util.List;
2122
import umc.cockple.demo.global.oauth2.service.KakaoOauthService;
@@ -283,5 +284,16 @@ private void validateCanWithdraw(Member member) {
283284
if (isLeader) {
284285
throw new MemberException(MemberErrorCode.MANAGER_CANNOT_LEAVE);
285286
}
287+
288+
// 활성화 된 모임의 부모임장인 경우 -> 탈퇴 불가
289+
boolean isSubOwner = member.getMemberParties().stream()
290+
.anyMatch(memberParty ->
291+
memberParty.getRole() == Role.party_SUBMANAGER
292+
&& memberParty.getStatus() == MemberPartyStatus.ACTIVE
293+
);
294+
295+
if (isSubOwner) {
296+
throw new MemberException(MemberErrorCode.SUBMANAGER_CANNOT_LEAVE);
297+
}
286298
}
287299
}

‎src/main/java/umc/cockple/demo/domain/notification/enums/NotificationTarget.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ public enum NotificationTarget {
88
PARTY_MODIFY(NotificationType.CHANGE),
99
PARTY_INVITE(NotificationType.INVITE),
1010
PARTY_INVITE_APPROVED(NotificationType.SIMPLE),
11-
PARTY_JOINREQUEST_APPROVED(NotificationType.CHANGE);
11+
PARTY_JOINREQUEST_APPROVED(NotificationType.CHANGE),
12+
PARTY_SUBOWNER_ASSIGNED(NotificationType.SIMPLE),
13+
PARTY_SUBOWNER_RELEASED(NotificationType.SIMPLE);
1214

1315
private final NotificationType defaultType;
1416

‎src/main/java/umc/cockple/demo/domain/notification/service/NotificationCommandService.java‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ public class NotificationCommandService {
4242
private final NotificationMessageGenerator notificationMessageGenerator;
4343
private final ObjectMapper objectMapper;
4444

45-
4645
// 알림 타입 변경 (초대 수락, 거절에 사용)
4746
public Response markAsReadNotification(Long memberId, Long notificationId, NotificationType type) {
4847
Notification notification = findByNotificationId(notificationId);
@@ -96,7 +95,11 @@ public void createNotification(CreateNotificationRequestDTO dto) {
9695
title = "새로운 모임";
9796
} else if (dto.target() == NotificationTarget.PARTY_INVITE_APPROVED) {
9897
content = notificationMessageGenerator.generateInviteApprovedMessage(dto.subjectName());
99-
}else {
98+
} else if (dto.target() == NotificationTarget.PARTY_SUBOWNER_ASSIGNED) {
99+
content = notificationMessageGenerator.generateSubOwnerAssignedMessage(dto.subjectName());
100+
} else if (dto.target() == NotificationTarget.PARTY_SUBOWNER_RELEASED) {
101+
content = notificationMessageGenerator.generateSubOwnerReleasedMessage(dto.subjectName());
102+
} else {
100103
content = notificationMessageGenerator.generateJoinRequestApprovedMessage();
101104
}
102105

‎src/main/java/umc/cockple/demo/domain/notification/service/NotificationMessageGenerator.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@ public String generateExerciseChangedMessage(String dateStr) {
3636
public String generateExerciseAttendChangedMessage() {
3737
return "운동 참석으로 변경되었어요!";
3838
}
39+
40+
public String generateSubOwnerAssignedMessage(String nickname) {
41+
return String.format("'%s'님이 부모임장으로 지정되었습니다.", nickname);
42+
}
43+
44+
public String generateSubOwnerReleasedMessage(String nickname) {
45+
return String.format("'%s'님의 부모임장 권한이 해제되었습니다.", nickname);
46+
}
3947
}

‎src/main/java/umc/cockple/demo/domain/party/controller/PartyController.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,22 @@ public BaseResponse<Void> removeMember(
213213
return BaseResponse.success(CommonSuccessCode.OK);
214214
}
215215

216+
@PatchMapping("/parties/{partyId}/members/{memberId}/role")
217+
@Operation(summary = "멤버 역할(부모임장) 설정", description = "모임장이 특정 멤버를 부모임장으로 지정하거나 해제합니다.")
218+
@ApiResponse(responseCode = "200", description = "역할 변경 성공")
219+
@ApiResponse(responseCode = "400", description = "유효하지 않은 역할 값")
220+
@ApiResponse(responseCode = "403", description = "모임장 권한 없음 또는 모임장 역할 변경 시도")
221+
@ApiResponse(responseCode = "404", description = "존재하지 않는 모임 또는 멤버")
222+
public BaseResponse<Void> updateMemberRole(
223+
@PathVariable Long partyId,
224+
@PathVariable Long memberId,
225+
@RequestBody @Valid PartyMemberRoleDTO.Request request) {
226+
Long currentMemberId = SecurityUtil.getCurrentMemberId();
227+
228+
partyCommandService.updateMemberRole(partyId, memberId, currentMemberId, request);
229+
return BaseResponse.success(CommonSuccessCode.OK);
230+
}
231+
216232
@PostMapping("/parties/{partyId}/join-requests")
217233
@Operation(summary = "모임 가입 신청",
218234
description = "사용자가 특정 모임에 가입을 신청합니다")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package umc.cockple.demo.domain.party.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.NotNull;
5+
import umc.cockple.demo.global.enums.Role;
6+
7+
public class PartyMemberRoleDTO {
8+
9+
@Schema(name = "PartyMemberRoleRequest")
10+
public record Request(
11+
@NotNull(message = "역할 값은 필수입니다.") Role role) {
12+
}
13+
}

0 commit comments

Comments
 (0)