Skip to content

Commit

Permalink
feat: 슈퍼관리자 기본 기능 구현 (#26)
Browse files Browse the repository at this point in the history
* fix: body가 없는 응답을 noContent로 수정

* fix: /members 와 /members/details 로 엔드포인트 분리, 반환형 명시

* fix: Dto 변환 메서드명 수정 toDto -> from

* feat: Admin 생성 기능 구현

* feat: Admin 전체 조회 기능 구현

* feat: Admin 정보 수정 기능 구현

* feat: Admin 삭제 기능 구현 (soft delete)

* fix: AccountBaseEntity 필드 protected로 변경, 업데이트 메서드 추가

* fix: Admin 수정 시 Status를 포함하도록 수정

* fix: 비밀번호 확인 메서드 추출

* feat: CustomException, GlobalExceptionHandler 구현

* feat: 전체 조회 시 페이징 적용

* fix: ApiResponse 오타 수정
  • Loading branch information
kimday0326 authored Dec 26, 2023
1 parent f1d7c8c commit 9ab3384
Show file tree
Hide file tree
Showing 18 changed files with 402 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
package com.pgms.apimember.controller;

import java.net.URI;
import java.util.Collections;
import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.pgms.apimember.dto.request.AdminCreateRequest;
import com.pgms.apimember.dto.request.AdminUpdateRequest;
import com.pgms.apimember.dto.request.PageCondition;
import com.pgms.apimember.dto.response.AdminGetResponse;
import com.pgms.apimember.dto.response.MemberDetailGetResponse;
import com.pgms.apimember.dto.response.MemberSummaryGetResponse;
import com.pgms.apimember.service.AdminService;
import com.pgms.coredomain.response.ApiResponse;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;

@RestController
Expand All @@ -24,9 +39,49 @@ public class AdminController {

private final AdminService adminService;

// 슈퍼 관리자 기능
// TODO: 슈퍼 관리자인지 확인 필요 (security)
@PostMapping
public ResponseEntity<ApiResponse<Long>> createAdmin(@RequestBody @Valid AdminCreateRequest requestDto) {
final Long adminId = adminService.createAdmin(requestDto);
final URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(adminId)
.toUri();
return ResponseEntity.created(uri).body(ApiResponse.created(adminId));
}

@GetMapping
public ResponseEntity<ApiResponse<List<AdminGetResponse>>> getAdmins(
@ModelAttribute @Valid PageCondition pageCondition) {
return ResponseEntity.ok(ApiResponse.ok(adminService.getAdmins(pageCondition)));
}

@PatchMapping("/{adminId}")
public ResponseEntity<ApiResponse<Void>> updateAdmin(
@PathVariable Long adminId,
@RequestBody @NotNull AdminUpdateRequest requestDto) {
adminService.updateAdmin(adminId, requestDto);
return ResponseEntity.noContent().build();
}

@DeleteMapping
public ResponseEntity<ApiResponse<Void>> deleteAdmins(@RequestParam List<Long> adminIds) {
adminService.deleteAdmins(adminIds);
return ResponseEntity.noContent().build();
}

// 일반 관리자 기능
// TODO: 일반 관리자인지 확인 필요 (security)
@GetMapping("/members")
public ResponseEntity<ApiResponse<?>> getMembers(@RequestParam(required = false) List<Long> memberIds) {
return ResponseEntity.ok(ApiResponse.ok(adminService.getMembers(memberIds)));
public ResponseEntity<ApiResponse<List<MemberSummaryGetResponse>>> getMembers(
@ModelAttribute @Valid PageCondition pageCondition) {
return ResponseEntity.ok(ApiResponse.ok(adminService.getMembers(pageCondition)));
}

@GetMapping("/members/details")
public ResponseEntity<ApiResponse<List<MemberDetailGetResponse>>> getMemberDetails(
@RequestParam List<Long> memberIds) {
return ResponseEntity.ok(ApiResponse.ok(adminService.getMemberDetails(memberIds)));
}

@GetMapping("/me")
Expand All @@ -36,7 +91,7 @@ public ResponseEntity<ApiResponse<AdminGetResponse>> getMyInfo() {

@DeleteMapping("/me")
public ResponseEntity<ApiResponse<Void>> deleteMyAccount() {
adminService.deleteAdmin(TEMP_CURRENT_ID);
return ResponseEntity.ok(ApiResponse.ok(null));
adminService.deleteAdmins(Collections.singletonList(TEMP_CURRENT_ID));
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.pgms.apimember.dto.request;

import com.pgms.coredomain.domain.member.Admin;
import com.pgms.coredomain.domain.member.Role;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public record AdminCreateRequest(
@NotBlank(message = "이름을 입력해주세요.")
@Size(min = 2, max = 50, message = "이름은 2자 이상 50자 이하로 입력해주세요.")
String name,

@NotBlank(message = "비밀번호를 입력해주세요.")
@Size(min = 6, message = "비밀번호는 6자 이상 입력해주세요.")
String password,

@NotBlank(message = "비밀번호 확인을 입력해주세요.")
String passwordConfirm,

@NotBlank(message = "전화번호를 입력해주세요.")
@Pattern(regexp = "\\d+", message = "전화번호는 숫자만 입력해주세요.")
String phoneNumber,

@NotBlank(message = "이메일을 입력해주세요.")
@Email(message = "이메일 형식에 맞지 않습니다.")
String email,

@NotBlank(message = "역할을 입력해주세요.")
String roleName
) {
public static Admin toEntity(AdminCreateRequest requestDto, String encodedPassword, Role role) {
return Admin.builder()
.name(requestDto.name())
.password(encodedPassword)
.phoneNumber(requestDto.phoneNumber())
.email(requestDto.email())
.role(role)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.pgms.apimember.dto.request;

import com.pgms.coredomain.domain.member.enums.AccountStatus;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public record AdminUpdateRequest(
@NotBlank(message = "이름을 입력해주세요.")
@Size(min = 2, max = 50, message = "이름은 2자 이상 50자 이하로 입력해주세요.")
String name,

@NotBlank(message = "비밀번호를 입력해주세요.")
@Size(min = 6, message = "비밀번호는 6자 이상 입력해주세요.")
String password,

@NotBlank(message = "비밀번호 확인을 입력해주세요.")
String passwordConfirm,

@NotBlank(message = "전화번호를 입력해주세요.")
@Pattern(regexp = "\\d+", message = "전화번호는 숫자만 입력해주세요.")
String phoneNumber,

@NotBlank(message = "계정 상태를 입력해주세요.")
AccountStatus status,

@NotBlank(message = "역할을 입력해주세요.")
String roleName
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.pgms.apimember.dto.request;

import lombok.Getter;

@Getter
public class PageCondition {
private static final Integer DEFAULT_PAGE = 1;
private static final Integer DEFAULT_SIZE = 10;

private final Integer page;
private final Integer size;

public PageCondition(Integer page, Integer size) {
this.page = isValidPage(page) ? page : DEFAULT_PAGE;
this.size = isValidSize(size) ? size : DEFAULT_SIZE;
}

private Boolean isValidPage(Integer page) {
return page != null && page > 0;
}

private Boolean isValidSize(Integer size) {
return size != null && size > 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public record AdminGetResponse(
LocalDateTime updatedAt,
Role role
) {
public static AdminGetResponse toDto(Admin admin) {
public static AdminGetResponse from(Admin admin) {
return new AdminGetResponse(
admin.getId(),
admin.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public record MemberDetailGetResponse(
LocalDateTime updatedAt,
Role role
) {
public static MemberDetailGetResponse toDto(Member member) {
public static MemberDetailGetResponse from(Member member) {
return new MemberDetailGetResponse(
member.getId(),
member.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public record MemberSummaryGetResponse(
String phoneNumber,
String email
) {
public static MemberSummaryGetResponse toDto(Member member) {
public static MemberSummaryGetResponse from(Member member) {
return new MemberSummaryGetResponse(
member.getId(),
member.getName(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.pgms.apimember.exception;

import lombok.Getter;

@Getter
public class AdminException extends CustomException {

public AdminException(CustomErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.pgms.apimember.exception;

import org.springframework.http.HttpStatus;

import lombok.Getter;

@Getter
public enum CustomErrorCode {
// ADMIN
ADMIN_NOT_FOUND("NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 관리자입니다."),
DUPLICATED_ADMIN_EMAIL("DUPLICATED ADMIN EMAIL", HttpStatus.BAD_REQUEST, "이미 존재하는 관리자 이메일입니다."),
NOT_AUTHORIZED("NOT AUTHORIZED", HttpStatus.UNAUTHORIZED, "접근 권한이 없습니다."),
ADMIN_ROLE_NOT_FOUND("ADMIN ROLE NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 관리자 역할입니다."),
ADMIN_PERMISSION_NOT_FOUND("ADMIN PERMISSION NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 관리자 권한입니다."),

// MEMBER
MEMBER_NOT_FOUND("NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다."),
DUPLICATED_MEMBER_EMAIL("DUPLICATED MEMBER EMAIL", HttpStatus.BAD_REQUEST, "이미 존재하는 회원 이메일입니다."),
NOT_MATCH_PASSWORD("NOT MATCH PASSWORD", HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."),
NOT_MATCH_PASSWORD_CONFIRM("NOT MATCH PASSWORD CONFIRM", HttpStatus.BAD_REQUEST, "비밀번호 확인이 일치하지 않습니다."),
VALIDATION_FAILED("VALIDATION FAILED", HttpStatus.BAD_REQUEST, "입력값에 대한 검증에 실패했습니다.");

private final String errorCode;
private final HttpStatus status;
private final String message;

CustomErrorCode(String errorCode, HttpStatus status, String message) {
this.errorCode = errorCode;
this.status = status;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.pgms.apimember.exception;

import lombok.Getter;

@Getter
public class CustomException extends RuntimeException {

protected final CustomErrorCode errorCode;

public CustomException(CustomErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.pgms.apimember.exception;

import java.util.List;
import java.util.Objects;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.pgms.coredomain.response.ErrorResponse;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
log.error(">>>>> Internal Server Error : {}", ex);
ErrorResponse errorResponse = new ErrorResponse("INTERNAL SERVER ERROR", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}

@ExceptionHandler(CustomException.class)
protected ResponseEntity<ErrorResponse> handleEventCustomException(CustomException ex) {
log.warn(">>>>> Custom Exception : {}", ex);
CustomErrorCode errorCode = ex.getErrorCode();
ErrorResponse errorResponse = new ErrorResponse(errorCode.getErrorCode(), errorCode.getMessage());
return ResponseEntity.status(errorCode.getStatus()).body(errorResponse);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
log.warn(">>>>> validation Failed : {}", ex);
BindingResult bindingResult = ex.getBindingResult();
String errorMessage = Objects.requireNonNull(bindingResult.getFieldError())
.getDefaultMessage();

List<FieldError> fieldErrors = bindingResult.getFieldErrors();
ErrorResponse errorResponse = new ErrorResponse(CustomErrorCode.VALIDATION_FAILED.getErrorCode(), errorMessage);
fieldErrors.forEach(error -> errorResponse.addValidation(error.getField(), error.getDefaultMessage()));
return ResponseEntity.status(ex.getStatusCode()).body(errorResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.pgms.apimember.exception;

import lombok.Getter;

@Getter
public class MemberException extends CustomException {

public MemberException(CustomErrorCode errorCode) {
super(errorCode);
}
}
Loading

0 comments on commit 9ab3384

Please sign in to comment.