Skip to content

Commit 3904b77

Browse files
authored
feat:관리자 계정 수정하기 구현 (#528)
* feat:관리자 계정 수정하기 구현 * feat:자신의 계정에 대한 권한 수정 차단
1 parent f349e2a commit 3904b77

6 files changed

Lines changed: 318 additions & 1 deletion

File tree

src/main/java/org/ject/support/admin/account/controller/AdminAccountController.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.ject.support.admin.account.dto.AdminAccountActiveUpdateRequest;
99
import org.ject.support.admin.account.dto.AdminAccountCreateRequest;
1010
import org.ject.support.admin.account.dto.AdminAccountRoleUpdateRequest;
11+
import org.ject.support.admin.account.dto.AdminAccountUpdateRequest;
1112
import org.ject.support.admin.account.service.AdminAccountService;
1213
import org.ject.support.common.security.AuthPrincipal;
1314
import org.springframework.security.access.prepost.PreAuthorize;
@@ -35,6 +36,17 @@ public void createAccount(@RequestBody @Valid final AdminAccountCreateRequest re
3536
adminAccountService.createAccount(request);
3637
}
3738

39+
@PatchMapping("/{memberId}")
40+
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
41+
@Operation(
42+
summary = "관리자 계정 정보 수정",
43+
description = "관리자 계정의 이메일, 이름, 권한, 활성화 상태를 수정합니다.")
44+
public void updateAccount(@Parameter(hidden = true) @AuthPrincipal final Long requesterId,
45+
@PathVariable final Long memberId,
46+
@RequestBody @Valid final AdminAccountUpdateRequest request) {
47+
adminAccountService.updateAccount(requesterId, memberId, request);
48+
}
49+
3850
@PatchMapping("/{memberId}/role")
3951
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
4052
@Operation(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.ject.support.admin.account.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.Email;
5+
import jakarta.validation.constraints.NotBlank;
6+
import jakarta.validation.constraints.NotNull;
7+
import jakarta.validation.constraints.Size;
8+
import org.ject.support.domain.member.Role;
9+
10+
@Schema(description = "관리자 계정 정보 수정 요청")
11+
public record AdminAccountUpdateRequest(
12+
@Schema(description = "관리자 계정 이메일", example = "admin@ject.kr", maxLength = 30)
13+
@NotBlank
14+
@Email(message = "올바른 이메일 형식이 아닙니다.")
15+
@Size(max = 30, message = "이메일 길이는 최대 30자리 까지 가능합니다.")
16+
String email,
17+
18+
@Schema(description = "관리자 이름", example = "김젝트", maxLength = 20, nullable = true)
19+
@Size(max = 20, message = "이름 길이는 최대 20자리 까지 가능합니다.")
20+
String name,
21+
22+
@Schema(
23+
description = "관리자 계정 유형",
24+
example = "OPERATIONS",
25+
allowableValues = {"ADMIN", "OPERATIONS", "SUPPORTER"})
26+
@NotNull
27+
Role role,
28+
29+
@Schema(description = "관리자 계정 활성화 여부", example = "true")
30+
@NotNull
31+
Boolean active
32+
) {
33+
34+
public String normalizeName() {
35+
if (name == null || name.isBlank()) {
36+
return null;
37+
}
38+
return name;
39+
}
40+
}

src/main/java/org/ject/support/admin/account/service/AdminAccountService.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.ject.support.admin.account.dto.AdminAccountActiveUpdateRequest;
55
import org.ject.support.admin.account.dto.AdminAccountCreateRequest;
66
import org.ject.support.admin.account.dto.AdminAccountRoleUpdateRequest;
7+
import org.ject.support.admin.account.dto.AdminAccountUpdateRequest;
78
import org.ject.support.admin.component.AdminMemberComponent;
89
import org.ject.support.admin.exception.AdminErrorCode;
910
import org.ject.support.admin.exception.AdminException;
@@ -34,6 +35,28 @@ public void createAccount(final AdminAccountCreateRequest request) {
3435
memberRepository.save(request.toEntity(encodedPassword));
3536
}
3637

38+
@Transactional
39+
public void updateAccount(final Long requesterId,
40+
final Long memberId,
41+
final AdminAccountUpdateRequest request) {
42+
validateBackofficeRole(request.role());
43+
validateNotLockingSelf(requesterId, memberId, request.active());
44+
validateNotChangingOwnAdminRole(requesterId, memberId, request.role());
45+
46+
final Member member = adminMemberComponent.getRequiredBackofficeMemberById(memberId);
47+
validateEmailUniqueness(memberId, request.email());
48+
49+
final MemberStatus status = request.active() ? MemberStatus.ACTIVE : MemberStatus.LOCKED;
50+
final MemberEditor editor = member.toEditor()
51+
.email(request.email())
52+
.name(request.normalizeName())
53+
.role(request.role())
54+
.status(status)
55+
.build();
56+
57+
member.edit(editor);
58+
}
59+
3760
@Transactional
3861
public void updateRole(final Long memberId, final AdminAccountRoleUpdateRequest request) {
3962
validateBackofficeRole(request.role());
@@ -67,6 +90,14 @@ private void validateNotLockingSelf(final Long requesterId,
6790
}
6891
}
6992

93+
private void validateNotChangingOwnAdminRole(final Long requesterId,
94+
final Long memberId,
95+
final Role role) {
96+
if (requesterId.equals(memberId) && role != Role.ADMIN) {
97+
throw new AdminException(AdminErrorCode.CANNOT_CHANGE_OWN_ADMIN_ROLE);
98+
}
99+
}
100+
70101
private void validateBackofficeRole(final Role role) {
71102
if (!role.isBackoffice()) {
72103
throw new AdminException(AdminErrorCode.INVALID_ADMIN_ACCOUNT_ROLE);
@@ -78,4 +109,12 @@ private void validateEmailUniqueness(final AdminAccountCreateRequest request) {
78109
throw new AdminException(AdminErrorCode.DUPLICATE_ADMIN_EMAIL);
79110
}
80111
}
112+
113+
private void validateEmailUniqueness(final Long memberId, final String email) {
114+
memberRepository.findByEmail(email)
115+
.filter(member -> !member.getId().equals(memberId))
116+
.ifPresent(member -> {
117+
throw new AdminException(AdminErrorCode.DUPLICATE_ADMIN_EMAIL);
118+
});
119+
}
81120
}

src/main/java/org/ject/support/admin/exception/AdminErrorCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ public enum AdminErrorCode implements ErrorCode {
1414
LOGIN_ATTEMPT_LIMITED(HttpStatus.TOO_MANY_REQUESTS, "ADMIN-7", "로그인 시도가 제한되었습니다. 잠시 후 다시 시도해주세요."),
1515
DUPLICATE_ADMIN_EMAIL(HttpStatus.CONFLICT, "ADMIN-8", "이미 사용중인 이메일 입니다."),
1616
INVALID_ADMIN_ACCOUNT_ROLE(HttpStatus.BAD_REQUEST, "ADMIN-9", "관리자 계정 유형만 선택할 수 있습니다."),
17-
CANNOT_LOCK_SELF(HttpStatus.BAD_REQUEST, "ADMIN-10", "본인 계정은 비활성화할 수 없습니다.");
17+
CANNOT_LOCK_SELF(HttpStatus.BAD_REQUEST, "ADMIN-10", "본인 계정은 비활성화할 수 없습니다."),
18+
CANNOT_CHANGE_OWN_ADMIN_ROLE(HttpStatus.BAD_REQUEST, "ADMIN-11", "본인 계정의 관리자 권한은 변경할 수 없습니다.");
1819

1920
private final HttpStatus httpStatus;
2021
private final String code;

src/test/java/org/ject/support/admin/account/controller/AdminAccountControllerTest.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.ject.support.admin.account.dto.AdminAccountActiveUpdateRequest;
55
import org.ject.support.admin.account.dto.AdminAccountCreateRequest;
66
import org.ject.support.admin.account.dto.AdminAccountRoleUpdateRequest;
7+
import org.ject.support.admin.account.dto.AdminAccountUpdateRequest;
78
import org.ject.support.admin.account.service.AdminAccountService;
89
import org.ject.support.base.UnitTestSupport;
910
import org.ject.support.common.exception.GlobalErrorCode;
@@ -88,6 +89,86 @@ void tearDown() {
8889
assertThat(preAuthorize.value()).isEqualTo("hasAuthority('ROLE_ADMIN')");
8990
}
9091

92+
@Test
93+
void 관리자_계정_정보_수정_성공() throws Exception {
94+
// given
95+
var requesterId = 2L;
96+
var memberId = 1L;
97+
var request = new AdminAccountUpdateRequest("updated@ject.kr", "이젝트", Role.SUPPORTER, false);
98+
setAuthentication(requesterId);
99+
100+
doNothing().when(adminAccountService)
101+
.updateAccount(eq(requesterId), eq(memberId), any(AdminAccountUpdateRequest.class));
102+
103+
// when, then
104+
mockMvc.perform(patch("/admin/accounts/{memberId}", memberId)
105+
.contentType(MediaType.APPLICATION_JSON)
106+
.content(objectMapper.writeValueAsString(request)))
107+
.andExpect(status().isOk())
108+
.andDo(print());
109+
110+
verify(adminAccountService)
111+
.updateAccount(eq(requesterId), eq(memberId), any(AdminAccountUpdateRequest.class));
112+
}
113+
114+
@Test
115+
void 관리자_계정_정보_수정은_ADMIN_권한만_허용한다() throws Exception {
116+
// when
117+
PreAuthorize preAuthorize = AdminAccountController.class
118+
.getMethod("updateAccount", Long.class, Long.class, AdminAccountUpdateRequest.class)
119+
.getAnnotation(PreAuthorize.class);
120+
121+
// when, then
122+
assertThat(preAuthorize).isNotNull();
123+
assertThat(preAuthorize.value()).isEqualTo("hasAuthority('ROLE_ADMIN')");
124+
}
125+
126+
@Test
127+
void 관리자_계정_정보_수정_실패_올바르지_않은_이메일_형식() throws Exception {
128+
// given
129+
var request = new AdminAccountUpdateRequest("invalid-email", "김젝트", Role.OPERATIONS, true);
130+
setAuthentication(2L);
131+
132+
// when, then
133+
mockMvc.perform(patch("/admin/accounts/{memberId}", 1L)
134+
.contentType(MediaType.APPLICATION_JSON)
135+
.content(objectMapper.writeValueAsString(request)))
136+
.andExpect(status().isBadRequest())
137+
.andExpect(jsonPath("$.code").value(GlobalErrorCode.METHOD_VALIDATION_FAILED.getCode()))
138+
.andExpect(jsonPath("$.messages[0]").value("올바른 이메일 형식이 아닙니다."))
139+
.andDo(print());
140+
}
141+
142+
@Test
143+
void 관리자_계정_정보_수정_실패_role_누락() throws Exception {
144+
// given
145+
var request = new AdminAccountUpdateRequest("admin@ject.kr", "김젝트", null, true);
146+
setAuthentication(2L);
147+
148+
// when, then
149+
mockMvc.perform(patch("/admin/accounts/{memberId}", 1L)
150+
.contentType(MediaType.APPLICATION_JSON)
151+
.content(objectMapper.writeValueAsString(request)))
152+
.andExpect(status().isBadRequest())
153+
.andExpect(jsonPath("$.code").value(GlobalErrorCode.METHOD_VALIDATION_FAILED.getCode()))
154+
.andDo(print());
155+
}
156+
157+
@Test
158+
void 관리자_계정_정보_수정_실패_active_누락() throws Exception {
159+
// given
160+
var request = new AdminAccountUpdateRequest("admin@ject.kr", "김젝트", Role.OPERATIONS, null);
161+
setAuthentication(2L);
162+
163+
// when, then
164+
mockMvc.perform(patch("/admin/accounts/{memberId}", 1L)
165+
.contentType(MediaType.APPLICATION_JSON)
166+
.content(objectMapper.writeValueAsString(request)))
167+
.andExpect(status().isBadRequest())
168+
.andExpect(jsonPath("$.code").value(GlobalErrorCode.METHOD_VALIDATION_FAILED.getCode()))
169+
.andDo(print());
170+
}
171+
91172
@Test
92173
void 관리자_계정_권한_수정_성공() throws Exception {
93174
// given

0 commit comments

Comments
 (0)