diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/controller/.gitkeep b/src/main/java/com/doubleo/adminservice/domain/admin/controller/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/controller/AdminController.java b/src/main/java/com/doubleo/adminservice/domain/admin/controller/AdminController.java new file mode 100644 index 0000000..19d37b0 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/admin/controller/AdminController.java @@ -0,0 +1,34 @@ +package com.doubleo.adminservice.domain.admin.controller; + +import com.doubleo.adminservice.domain.admin.dto.request.AdminPwUpdateRequest; +import com.doubleo.adminservice.domain.admin.dto.response.AdminInfoResponse; +import com.doubleo.adminservice.domain.admin.service.AdminService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "1-1. Admin API", description = "관리자 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/admins") +public class AdminController { + private final AdminService adminService; + + @Operation(summary = "관리자 본인 정보 조회", description = "관리자 본인 정보를 조회합니다.") + @GetMapping("/me") + public AdminInfoResponse adminGet(@RequestHeader("X-Admin-Id") Long adminId) { + return adminService.getAdminInfo(adminId); + } + + @Operation(summary = "관리자 비밀번호 업데이트", description = "관리자 비밀번호를 업데이트합니다.") + @PatchMapping("/me/password") + public ResponseEntity adminPasswordUpdate( + @RequestHeader("X-Admin-Id") Long adminId, + @Valid @RequestBody AdminPwUpdateRequest request) { + adminService.updateAdminPassword(adminId, request); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/dto/request/.gitkeep b/src/main/java/com/doubleo/adminservice/domain/admin/dto/request/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/dto/request/AdminPwUpdateRequest.java b/src/main/java/com/doubleo/adminservice/domain/admin/dto/request/AdminPwUpdateRequest.java new file mode 100644 index 0000000..d51a217 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/admin/dto/request/AdminPwUpdateRequest.java @@ -0,0 +1,12 @@ +package com.doubleo.adminservice.domain.admin.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +public record AdminPwUpdateRequest( + @Schema(description = "관리자 기존 패스워드", example = "pw12345") + @NotBlank(message = "기존 비밀번호는 필수입니다.") + String passwordOriginal, + @Schema(description = "관리자 신규 패스워드", example = "pw67890") + @NotBlank(message = "신규 비밀번호는 필수입니다.") + String passwordNew) {} diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/dto/response/.gitkeep b/src/main/java/com/doubleo/adminservice/domain/admin/dto/response/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/dto/response/AdminInfoResponse.java b/src/main/java/com/doubleo/adminservice/domain/admin/dto/response/AdminInfoResponse.java new file mode 100644 index 0000000..d35c32c --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/admin/dto/response/AdminInfoResponse.java @@ -0,0 +1,15 @@ +package com.doubleo.adminservice.domain.admin.dto.response; + +import com.doubleo.adminservice.domain.admin.domain.Admin; +import io.swagger.v3.oas.annotations.media.Schema; + +public record AdminInfoResponse( + @Schema(description = "관리자 ID", example = "1") Long adminId, + @Schema(description = "관리자 username", example = "example@gmail.com") String username, + @Schema(description = "관리자 이름", example = "정선우") String name, + @Schema(description = "관리자 연락처", example = "010-1234-5678") String contact) { + public static AdminInfoResponse of(Admin admin) { + return new AdminInfoResponse( + admin.getId(), admin.getUsername(), admin.getName(), admin.getContact()); + } +} diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/repository/.gitkeep b/src/main/java/com/doubleo/adminservice/domain/admin/repository/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/service/.gitkeep b/src/main/java/com/doubleo/adminservice/domain/admin/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminService.java b/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminService.java new file mode 100644 index 0000000..67c9162 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminService.java @@ -0,0 +1,11 @@ +package com.doubleo.adminservice.domain.admin.service; + +import com.doubleo.adminservice.domain.admin.dto.request.AdminPwUpdateRequest; +import com.doubleo.adminservice.domain.admin.dto.response.AdminInfoResponse; + +public interface AdminService { + + AdminInfoResponse getAdminInfo(Long adminId); + + void updateAdminPassword(Long adminId, AdminPwUpdateRequest request); +} diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminServiceImpl.java b/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminServiceImpl.java new file mode 100644 index 0000000..45dd8dd --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminServiceImpl.java @@ -0,0 +1,53 @@ +package com.doubleo.adminservice.domain.admin.service; + +import com.doubleo.adminservice.domain.admin.domain.Admin; +import com.doubleo.adminservice.domain.admin.dto.request.AdminPwUpdateRequest; +import com.doubleo.adminservice.domain.admin.dto.response.AdminInfoResponse; +import com.doubleo.adminservice.domain.admin.repository.AdminRepository; +import com.doubleo.adminservice.global.exception.CommonException; +import com.doubleo.adminservice.global.exception.errorcode.AdminErrorCode; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@Transactional +@RequiredArgsConstructor +public class AdminServiceImpl implements AdminService { + + private final AdminRepository adminRepository; + private final BCryptPasswordEncoder passwordEncoder; + + @Override + public AdminInfoResponse getAdminInfo(Long adminId) { + Admin admin = findAdmin(adminId); + return AdminInfoResponse.of(admin); + } + + public void updateAdminPassword(Long adminId, AdminPwUpdateRequest request) { + Admin admin = findAdmin(adminId); + validateAdminPassword(request.passwordOriginal(), admin.getPassword()); + isPasswordNew(request.passwordNew(), admin.getPassword()); + admin.updateAdminPassword(passwordEncoder.encode(request.passwordNew())); + } + + // util + private Admin findAdmin(Long adminId) { + return adminRepository + .findById(adminId) + .orElseThrow(() -> new CommonException(AdminErrorCode.ADMIN_NOT_FOUND)); + } + + private void validateAdminPassword(String raw, String encoded) { + if (!passwordEncoder.matches(raw, encoded)) { + throw new CommonException(AdminErrorCode.INVALID_PASSWORD); + } + } + + private void isPasswordNew(String raw, String encoded) { + if (passwordEncoder.matches(raw, encoded)) { + throw new CommonException(AdminErrorCode.DUPLICATED_PASSWORD); + } + } +} diff --git a/src/test/java/com/doubleo/adminservice/service/AdminServiceTest.java b/src/test/java/com/doubleo/adminservice/service/AdminServiceTest.java new file mode 100644 index 0000000..d2c8e0e --- /dev/null +++ b/src/test/java/com/doubleo/adminservice/service/AdminServiceTest.java @@ -0,0 +1,118 @@ +package com.doubleo.adminservice.service; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.BDDMockito.*; + +import com.doubleo.adminservice.domain.admin.domain.Admin; +import com.doubleo.adminservice.domain.admin.dto.request.AdminPwUpdateRequest; +import com.doubleo.adminservice.domain.admin.dto.response.AdminInfoResponse; +import com.doubleo.adminservice.domain.admin.repository.AdminRepository; +import com.doubleo.adminservice.domain.admin.service.AdminServiceImpl; +import com.doubleo.adminservice.global.exception.CommonException; +import com.doubleo.adminservice.global.exception.errorcode.AdminErrorCode; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.test.util.ReflectionTestUtils; + +@ExtendWith(MockitoExtension.class) +public class AdminServiceTest { + + @InjectMocks private AdminServiceImpl adminService; + + @Mock private AdminRepository adminRepository; + + @Mock private BCryptPasswordEncoder bCryptPasswordEncoder; + + private final String username = "test@test.com"; + private final String password = "password"; + private final String name = "name"; + private final String contact = "contact"; + + private Admin admin; + + @BeforeEach + void setUp() { + admin = Admin.createAdmin(username, "encoded", name, contact); + ReflectionTestUtils.setField(admin, "id", 1L); + } + + @Nested + class getAdminInfo { + + @Test + void 관리자정보_조회하면_정상적으로_반환된다() { + // given + given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + + // when + AdminInfoResponse response = adminService.getAdminInfo(admin.getId()); + + // then + assertThat(response.adminId()).isEqualTo(admin.getId()); + assertThat(response.username()).isEqualTo(admin.getUsername()); + assertThat(response.name()).isEqualTo(admin.getName()); + assertThat(response.contact()).isEqualTo(admin.getContact()); + } + } + + @Nested + class updateAdminPassword { + + @Test + void 비밀번호_변경하면_정상적으로_변경된다() { + // given + String newPassword = "newPassword"; + String encodedNewPassword = "encodedNew"; + + given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + given(bCryptPasswordEncoder.matches(password, "encoded")).willReturn(true); + given(bCryptPasswordEncoder.encode(newPassword)).willReturn(encodedNewPassword); + + // when + adminService.updateAdminPassword(1L, new AdminPwUpdateRequest(password, newPassword)); + + // then + assertThat(admin.getPassword()).isEqualTo(encodedNewPassword); + } + + @Test + void 기존_비밀번호_유효하지_않으면_오류_발생한다() { + // given + given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + given(bCryptPasswordEncoder.matches("wrongPassword", "encoded")).willReturn(false); + + // when & then + assertThatThrownBy( + () -> + adminService.updateAdminPassword( + 1L, + new AdminPwUpdateRequest( + "wrongPassword", "newPassword"))) + .isInstanceOf(CommonException.class) + .hasMessage(AdminErrorCode.INVALID_PASSWORD.getMessage()); + } + + @Test + void 기존_비밀번호와_신규_비밀번호가_동일하면_오류_발생한다() { + // given + given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + given(bCryptPasswordEncoder.matches(password, "encoded")).willReturn(true); + + // when & then + assertThatThrownBy( + () -> + adminService.updateAdminPassword( + 1L, new AdminPwUpdateRequest(password, password))) + .isInstanceOf(CommonException.class) + .hasMessage(AdminErrorCode.DUPLICATED_PASSWORD.getMessage()); + } + } +}