@@ -26,10 +26,13 @@ public class UserAdminService {
2626
2727 private final UserRepository userRepository ;
2828
29+ /* ======================= 목록 ======================= */
30+
2931 @ Transactional (readOnly = true )
30- public Page <UserSummaryResponse > listUsers (String q , Pageable pageable ) {
32+ public Page <UserSummaryResponse > listUsers (String q , UserRole role , TeamType team , Pageable pageable ) {
3133 Pageable fixed = rewriteSort (pageable );
32- return userRepository .findSummaries (q , fixed );
34+ // 레포지토리에 role/team 조건 추가한 메서드가 있어야 함
35+ return userRepository .findSummaries (q , role , team , fixed );
3336 }
3437
3538 private Pageable rewriteSort (Pageable pageable ) {
@@ -45,40 +48,23 @@ private Pageable rewriteSort(Pageable pageable) {
4548
4649 if ("userRole" .equals (prop )) {
4750 hasUserRoleOrder = true ;
48- String roleRankCase = "CASE u.userRole " +
49- "WHEN 'GUEST' THEN 0 " +
50- "WHEN 'MEMBER' THEN 1 " +
51- "WHEN 'CORE' THEN 2 " +
52- "WHEN 'LEAD' THEN 3 " +
53- "WHEN 'ORGANIZER' THEN 4 " +
54- "WHEN 'ADMIN' THEN 5 " +
55- "ELSE -1 END" ;
51+ String roleRankCase = "CASE u.userRole " + "WHEN 'GUEST' THEN 0 " + "WHEN 'MEMBER' THEN 1 " + "WHEN 'CORE' THEN 2 " + "WHEN 'LEAD' THEN 3 " + "WHEN 'ORGANIZER' THEN 4 " + "WHEN 'ADMIN' THEN 5 " + "ELSE -1 END" ;
5652 composed = composed .and (JpaSort .unsafe (dir , roleRankCase ));
5753 } else {
5854 composed = composed .and (Sort .by (new Sort .Order (dir , prop )));
5955 }
6056 }
6157
62- // ROLE 정렬 요청이 있었다면, 같은 권한 내에서 name ASC로 안정화
58+ // ROLE 정렬이 있으면 같은 권한 내 name ASC로 안정화
6359 if (hasUserRoleOrder ) {
6460 composed = composed .and (Sort .by ("name" ).ascending ());
6561 }
6662
6763 return PageRequest .of (pageable .getPageNumber (), pageable .getPageSize (), composed );
6864 }
6965
70- /**
71- * 역할/팀 동시 수정 API (PATCH /admin/users/{userId}/role-team)
72- * - 공통 규칙:
73- * 에디터의 role은 타겟의 현재/신규 role보다 "엄격히 높아야" 함
74- * - ADMIN: 자기 자신 강등 금지
75- * - ORGANIZER: ADMIN 대상 수정 금지
76- * - LEAD:
77- * - MEMBER/CORE만 수정 가능, 변경도 MEMBER/CORE로만
78- * - HR-LEAD: 자기 자신 제외 타인의 팀 변경 가능
79- * - 그 외 LEAD: 같은 팀 구성원만 수정 가능, 팀 변경 불가
80- * - 팀 보유 가능 역할: CORE, LEAD (그 외는 팀 자동 null)
81- */
66+ /* ======================= 수정 ======================= */
67+
8268 @ Transactional
8369 public void updateRoleAndTeam (CustomUserDetails editor , Long targetUserId , UpdateUserRoleTeamRequest req ) {
8470 User editorUser = getEditor (editor );
@@ -91,10 +77,10 @@ public void updateRoleAndTeam(CustomUserDetails editor, Long targetUserId, Updat
9177 UserRole newRole = (req .role () != null ? req .role () : targetCurrentRole );
9278 TeamType requestedTeam = (req .team () != null ? req .team () : target .getTeam ());
9379
94- // ✅ 팀 보유 가능한 역할만 팀 유지/지정 (CORE, LEAD만 가능 )
80+ // 팀 보유 가능한 역할만 팀 허용 (CORE, LEAD )
9581 TeamType newTeam = isTeamAssignableRole (newRole ) ? requestedTeam : null ;
9682
97- // 공통: 에디터는 타겟의 현재/신규 role보다 높아야 함 (동급 불가)
83+ // 공통: 에디터는 대상의 현재/신규 role보다 엄격히 높아야 함
9884 if (!(editorRole .rank () > targetCurrentRole .rank ())) {
9985 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "동급/상위 사용자의 정보는 변경할 수 없습니다." );
10086 }
@@ -104,14 +90,11 @@ public void updateRoleAndTeam(CustomUserDetails editor, Long targetUserId, Updat
10490
10591 switch (editorRole ) {
10692 case ADMIN -> {
107- // 자기 자신 강등 금지
10893 if (editorUser .getId ().equals (target .getId ()) && newRole .rank () < UserRole .ADMIN .rank ()) {
10994 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "자기 자신을 강등할 수 없습니다." );
11095 }
111- // ADMIN은 팀 변경 제한 없음 (위 정규화로 팀 자동 정리)
11296 }
11397 case ORGANIZER -> {
114- // ADMIN 대상은 수정 금지
11598 if (targetCurrentRole == UserRole .ADMIN ) {
11699 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "ADMIN 사용자는 수정할 수 없습니다." );
117100 }
@@ -120,7 +103,6 @@ public void updateRoleAndTeam(CustomUserDetails editor, Long targetUserId, Updat
120103 if (editor .getTeam () == null ) {
121104 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "LEAD 토큰에 팀 정보가 없습니다." );
122105 }
123- // LEAD는 MEMBER/CORE만 수정 가능, 변경도 MEMBER/CORE로만
124106 if (!(targetCurrentRole == UserRole .MEMBER || targetCurrentRole == UserRole .CORE )) {
125107 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "LEAD는 MEMBER/CORE만 수정할 수 있습니다." );
126108 }
@@ -129,15 +111,13 @@ public void updateRoleAndTeam(CustomUserDetails editor, Long targetUserId, Updat
129111 }
130112
131113 if (editor .getTeam () == TeamType .HR ) {
132- // HR-LEAD: 본인 제외 타인 팀 변경 가능
114+ // HR-LEAD: 본인 제외 타인지원 팀 변경 가능
133115 if (editorUser .getId ().equals (target .getId ())) {
134- // 본인은 팀 변경 불가
135116 if (req .team () != null && !Objects .equals (req .team (), target .getTeam ())) {
136117 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "HR-LEAD도 자기 자신의 팀은 변경할 수 없습니다." );
137118 }
138119 }
139120 } else {
140- // 일반 LEAD: 같은 팀 구성원만, 팀 변경 불가
141121 if (target .getTeam () != editor .getTeam ()) {
142122 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "다른 팀 사용자는 수정할 수 없습니다." );
143123 }
@@ -149,16 +129,9 @@ public void updateRoleAndTeam(CustomUserDetails editor, Long targetUserId, Updat
149129 default -> throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER );
150130 }
151131
152- // ✅ 최종 반영 (역할이 팀 불가면 팀은 자동 null 처리)
153132 targetChange (target , newRole , newTeam );
154133 }
155134
156- /**
157- * 역할만 수정 API (PATCH /admin/users/{userId}/role)
158- * - HR-CORE 특례: GUEST -> MEMBER 가능 (그 외 불가)
159- * - 일반 규칙: 에디터의 role은 대상의 현재/신규 role보다 엄격히 높아야 함
160- * - 역할이 팀 불가가 되면 팀은 자동 null 처리
161- */
162135 @ Transactional
163136 public void updateUserRoleWithRules (CustomUserDetails me , Long targetUserId , UserRole newRole ) {
164137 var meRole = me .getRole ();
@@ -173,82 +146,61 @@ public void updateUserRoleWithRules(CustomUserDetails me, Long targetUserId, Use
173146
174147 UserRole current = target .getUserRole ();
175148
176- // ✅ HR-CORE 특례: GUEST → MEMBER 만 허용
149+ // HR-CORE 특례: GUEST -> MEMBER
177150 boolean isHrCore = (meRole == UserRole .CORE ) && (meTeam == TeamType .HR );
178151 if (isHrCore ) {
179152 if (current == UserRole .GUEST && newRole == UserRole .MEMBER ) {
180153 target .changeRole (UserRole .MEMBER );
181- // MEMBER는 팀 불가 → 자동 null
182- if (!isTeamAssignableRole (UserRole .MEMBER )) {
183- target .changeTeam (null );
184- }
154+ if (!isTeamAssignableRole (UserRole .MEMBER )) target .changeTeam (null );
185155 userRepository .save (target );
186156 return ;
187157 }
188158 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "HR-CORE는 GUEST→MEMBER 변경만 가능" );
189159 }
190160
191- // 일반 규칙: 나의 권한은 대상의 현재/신규 권한보다 엄격히 높아야 함
192161 boolean higherThanCurrent = meRole .rank () > current .rank ();
193162 boolean higherThanNew = meRole .rank () > newRole .rank ();
194163 if (!higherThanCurrent || !higherThanNew ) {
195164 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "요청한 역할 변경 권한이 없습니다." );
196165 }
197166
198167 target .changeRole (newRole );
199- // 역할이 팀 불가면 팀 자동 해제
200- if (!isTeamAssignableRole (newRole )) {
201- target .changeTeam (null );
202- }
168+ if (!isTeamAssignableRole (newRole )) target .changeTeam (null );
203169 userRepository .save (target );
204170 }
205171
206172 @ Transactional
207173 public void deleteUserWithRules (CustomUserDetails me , Long targetUserId ) {
208174 User editor = userRepository .findById (me .getUserId ())
209175 .orElseThrow (() -> new BusinessException (GlobalErrorCode .UNAUTHORIZED_USER ));
210-
211176 User target = userRepository .findById (targetUserId )
212177 .orElseThrow (() -> new BusinessException (GlobalErrorCode .RESOURCE_NOT_FOUND ));
213178
214- // 자기 자신 삭제 금지
215179 if (Objects .equals (editor .getId (), target .getId ())) {
216180 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "자기 자신은 삭제할 수 없습니다." );
217181 }
218182
219183 UserRole editorRole = editor .getUserRole ();
220184 TeamType editorTeam = editor .getTeam ();
221-
222185 UserRole targetRole = target .getUserRole ();
223186 TeamType targetTeam = target .getTeam ();
224187
225- // 공통: '나'는 대상의 현재 role보다 "엄격히" 높아야 함
226188 if (!(editorRole .rank () > targetRole .rank ())) {
227189 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "동급/상급 사용자는 삭제할 수 없습니다." );
228190 }
229191
230192 switch (editorRole ) {
231- case ADMIN -> {
232- // ADMIN: 모두 삭제 가능(단, 자기 자신은 위에서 금지)
233- // 추가 보호가 필요하면 여기서 ADMIN→ADMIN 삭제 금지도 가능
234- }
193+ case ADMIN -> {}
235194 case ORGANIZER -> {
236- // ORGANIZER: ADMIN 삭제 불가(공통 검사로 이미 걸러짐). 그 외 삭제 가능
237195 if (targetRole == UserRole .ADMIN ) {
238196 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "ADMIN 사용자는 삭제할 수 없습니다." );
239197 }
240198 }
241199 case LEAD -> {
242- // LEAD: MEMBER/CORE만 삭제 가능
243200 if (!(targetRole == UserRole .MEMBER || targetRole == UserRole .CORE )) {
244201 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "LEAD는 MEMBER/CORE만 삭제할 수 있습니다." );
245202 }
246-
247- // HR-LEAD 특례: 본인 제외 누구든 팀 무관 삭제 가능
248- if (editorTeam == TeamType .HR ) {
249- // 자기 자신은 위에서 이미 금지
250- } else {
251- // 일반 LEAD: 같은 팀만 삭제 가능
203+ if (editorTeam != TeamType .HR ) {
252204 if (editorTeam == null || targetTeam != editorTeam ) {
253205 throw new BusinessException (GlobalErrorCode .FORBIDDEN_USER , "다른 팀 사용자는 삭제할 수 없습니다." );
254206 }
@@ -260,14 +212,9 @@ public void deleteUserWithRules(CustomUserDetails me, Long targetUserId) {
260212 userRepository .delete (target );
261213 }
262214
263- /**
264- * 실제 반영 (역할 변경 후 역할 정책에 따라 팀도 정리)
265- */
266215 private void targetChange (User target , UserRole newRole , TeamType newTeam ) {
267216 target .changeRole (newRole );
268- if (!isTeamAssignableRole (newRole )) {
269- newTeam = null ; // 방어적 정리
270- }
217+ if (!isTeamAssignableRole (newRole )) newTeam = null ;
271218 target .changeTeam (newTeam );
272219 userRepository .save (target );
273220 }
@@ -277,9 +224,6 @@ private User getEditor(CustomUserDetails editor) {
277224 .orElseThrow (() -> new BusinessException (GlobalErrorCode .UNAUTHORIZED_USER ));
278225 }
279226
280- /**
281- * 팀을 가질 수 있는 역할만 true (CORE, LEAD)
282- */
283227 private boolean isTeamAssignableRole (UserRole role ) {
284228 return role == UserRole .CORE || role == UserRole .LEAD ;
285229 }
0 commit comments