Merge branch 'dev' into main
Deploy / deploy (push) Failing after 11m27s
CI / build (push) Failing after 13m7s

This commit is contained in:
ByungCheol
2026-05-31 19:01:43 +09:00
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;
}
}
@@ -0,0 +1,37 @@
package com.sb.web.auth.dto;
import com.sb.web.auth.domain.Member;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 관리자용 회원 정보 응답 (비밀번호 제외).
*/
@Data
@Builder
public class MemberAdminResponse {
private Long id;
private String loginId;
private String name;
private String email;
private String role; // USER / ADMIN
private String status; // ACTIVE / SUSPENDED / WITHDRAWN
private String provider; // LOCAL / 소셜
private LocalDateTime createdAt;
public static MemberAdminResponse from(Member m) {
return MemberAdminResponse.builder()
.id(m.getId())
.loginId(m.getLoginId())
.name(m.getName())
.email(m.getEmail())
.role(m.getRole())
.status(m.getStatus())
.provider(m.getProvider())
.createdAt(m.getCreatedAt())
.build();
}
}
@@ -4,6 +4,8 @@ import com.sb.web.auth.domain.Member;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List;
/** /**
* 회원 MyBatis 매퍼. SQL 은 resources/mapper/MemberMapper.xml 에 정의. * 회원 MyBatis 매퍼. SQL 은 resources/mapper/MemberMapper.xml 에 정의.
*/ */
@@ -12,6 +14,9 @@ public interface MemberMapper {
Member findById(@Param("id") Long id); Member findById(@Param("id") Long id);
/** 관리자: 전체 회원 목록 */
List<Member> findAll();
Member findByLoginId(@Param("loginId") String loginId); Member findByLoginId(@Param("loginId") String loginId);
Member findByProvider(@Param("provider") String provider, Member findByProvider(@Param("provider") String provider,
@@ -22,4 +27,10 @@ public interface MemberMapper {
int insert(Member member); int insert(Member member);
int updatePassword(@Param("id") Long id, @Param("password") String password); int updatePassword(@Param("id") Long id, @Param("password") String password);
int updateRole(@Param("id") Long id, @Param("role") String role);
int updateStatus(@Param("id") Long id, @Param("status") String status);
int deleteById(@Param("id") Long id);
} }
@@ -25,6 +25,10 @@
SELECT <include refid="columns"/> FROM member WHERE id = #{id} SELECT <include refid="columns"/> FROM member WHERE id = #{id}
</select> </select>
<select id="findAll" resultMap="memberResultMap">
SELECT <include refid="columns"/> FROM member ORDER BY id
</select>
<select id="findByLoginId" parameterType="string" resultMap="memberResultMap"> <select id="findByLoginId" parameterType="string" resultMap="memberResultMap">
SELECT <include refid="columns"/> FROM member WHERE login_id = #{loginId} SELECT <include refid="columns"/> FROM member WHERE login_id = #{loginId}
</select> </select>
@@ -50,4 +54,16 @@
UPDATE member SET password = #{password}, updated_at = NOW() WHERE id = #{id} UPDATE member SET password = #{password}, updated_at = NOW() WHERE id = #{id}
</update> </update>
<update id="updateRole">
UPDATE member SET role = #{role}, updated_at = NOW() WHERE id = #{id}
</update>
<update id="updateStatus">
UPDATE member SET status = #{status}, updated_at = NOW() WHERE id = #{id}
</update>
<delete id="deleteById">
DELETE FROM member WHERE id = #{id}
</delete>
</mapper> </mapper>