feat: 관리자 회원 관리 API (/api/admin/members)

목록/역할 변경/상태 변경/삭제. 본인 계정 변경·삭제 차단.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-05-31 19:01:41 +09:00
co-authored by Claude Opus 4.8
parent eb6b9830ce
commit 8eb2129f5e
7 changed files with 212 additions and 0 deletions
@@ -0,0 +1,54 @@
package com.sb.web.admin.controller;
import com.sb.web.admin.dto.RoleUpdateRequest;
import com.sb.web.admin.dto.StatusUpdateRequest;
import com.sb.web.admin.service.MemberAdminService;
import com.sb.web.auth.dto.MemberAdminResponse;
import com.sb.web.auth.dto.SessionUser;
import com.sb.web.auth.web.AuthInterceptor;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 관리자용 회원 관리 API. (/api/admin/members — AdminInterceptor 로 관리자만 접근)
*/
@RestController
@RequestMapping("/api/admin/members")
@RequiredArgsConstructor
public class MemberAdminController {
private final MemberAdminService memberAdminService;
@GetMapping
public List<MemberAdminResponse> list() {
return memberAdminService.list();
}
@PutMapping("/{id}/role")
public MemberAdminResponse updateRole(
@PathVariable Long id,
@Valid @RequestBody RoleUpdateRequest req,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
return memberAdminService.updateRole(id, req.getRole(), current.getId());
}
@PutMapping("/{id}/status")
public MemberAdminResponse updateStatus(
@PathVariable Long id,
@Valid @RequestBody StatusUpdateRequest req,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
return memberAdminService.updateStatus(id, req.getStatus(), current.getId());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(
@PathVariable Long id,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
memberAdminService.delete(id, current.getId());
return ResponseEntity.noContent().build();
}
}
@@ -0,0 +1,14 @@
package com.sb.web.admin.dto;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
/**
* 회원 역할 변경 요청.
*/
@Data
public class RoleUpdateRequest {
@Pattern(regexp = "USER|ADMIN", message = "역할은 USER 또는 ADMIN 이어야 합니다.")
private String role;
}
@@ -0,0 +1,14 @@
package com.sb.web.admin.dto;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
/**
* 회원 상태 변경 요청.
*/
@Data
public class StatusUpdateRequest {
@Pattern(regexp = "ACTIVE|SUSPENDED|WITHDRAWN", message = "상태 값이 올바르지 않습니다.")
private String status;
}
@@ -0,0 +1,66 @@
package com.sb.web.admin.service;
import com.sb.web.auth.domain.Member;
import com.sb.web.auth.dto.MemberAdminResponse;
import com.sb.web.auth.mapper.MemberMapper;
import com.sb.web.common.exception.ApiException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 관리자용 회원 관리 서비스. (목록/역할/상태/삭제)
* 본인 계정에 대한 역할·상태·삭제는 잠금 방지를 위해 차단한다.
*/
@Service
@RequiredArgsConstructor
public class MemberAdminService {
private final MemberMapper memberMapper;
public List<MemberAdminResponse> list() {
return memberMapper.findAll().stream().map(MemberAdminResponse::from).toList();
}
@Transactional
public MemberAdminResponse updateRole(Long targetId, String role, Long currentAdminId) {
Member target = mustFind(targetId);
if (targetId.equals(currentAdminId)) {
throw new ApiException(HttpStatus.BAD_REQUEST, "본인의 역할은 변경할 수 없습니다.");
}
memberMapper.updateRole(targetId, role);
target.setRole(role);
return MemberAdminResponse.from(target);
}
@Transactional
public MemberAdminResponse updateStatus(Long targetId, String status, Long currentAdminId) {
Member target = mustFind(targetId);
if (targetId.equals(currentAdminId)) {
throw new ApiException(HttpStatus.BAD_REQUEST, "본인의 상태는 변경할 수 없습니다.");
}
memberMapper.updateStatus(targetId, status);
target.setStatus(status);
return MemberAdminResponse.from(target);
}
@Transactional
public void delete(Long targetId, Long currentAdminId) {
mustFind(targetId);
if (targetId.equals(currentAdminId)) {
throw new ApiException(HttpStatus.BAD_REQUEST, "본인 계정은 삭제할 수 없습니다.");
}
memberMapper.deleteById(targetId);
}
private Member mustFind(Long id) {
Member m = memberMapper.findById(id);
if (m == null) {
throw new ApiException(HttpStatus.NOT_FOUND, "회원을 찾을 수 없습니다.");
}
return m;
}
}