diff --git a/backend/src/docs/asciidoc/index.adoc b/backend/src/docs/asciidoc/index.adoc index 1024c972..d1459ce5 100644 --- a/backend/src/docs/asciidoc/index.adoc +++ b/backend/src/docs/asciidoc/index.adoc @@ -54,7 +54,7 @@ include::{snippets}/faculty-find-all/http-response.adoc[] include::{snippets}/faculty-find-all/response-fields.adoc[] -=== `PATCH`: 비밀번호 수정 +=== `PATCH`: 자신의 비밀번호 수정 .HTTP Request include::{snippets}/member-change-password/http-request.adoc[] @@ -65,6 +65,18 @@ include::{snippets}/member-change-password/request-fields.adoc[] .HTTP Response include::{snippets}/member-change-password/http-response.adoc[] + +=== `PATCH`: 자신의 기본 정보 수정 + +.HTTP Request +include::{snippets}/member-change-info/http-request.adoc[] + +.Request Body +include::{snippets}/member-change-info/request-fields.adoc[] + +.HTTP Response +include::{snippets}/member-change-info/http-response.adoc[] + == 학과 === `GET`: 단과대학 목록 조회 diff --git a/backend/src/main/java/sw_css/member/api/MemberController.java b/backend/src/main/java/sw_css/member/api/MemberController.java index f7e5ec0a..41c34c94 100644 --- a/backend/src/main/java/sw_css/member/api/MemberController.java +++ b/backend/src/main/java/sw_css/member/api/MemberController.java @@ -11,8 +11,10 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import sw_css.member.application.MemberCommandService; import sw_css.member.application.MemberQueryService; -import sw_css.member.application.dto.request.ChangePasswordRequest; +import sw_css.member.application.dto.request.MemberChangePasswordRequest; +import sw_css.member.application.dto.request.MemberChangeInfoRequest; import sw_css.member.application.dto.response.StudentMemberResponse; import sw_css.member.domain.Member; import sw_css.utils.annotation.MemberInterface; @@ -24,6 +26,7 @@ @Transactional public class MemberController { private final MemberQueryService memberQueryService; + private final MemberCommandService memberCommandService; @GetMapping("/{memberId}") public ResponseEntity findStudent(@PathVariable final Long memberId) { @@ -32,8 +35,15 @@ public ResponseEntity findStudent(@PathVariable final Lon @PatchMapping("/change-password") public ResponseEntity changeMemberPassword(@MemberInterface Member me, - @RequestBody @Valid ChangePasswordRequest request) { - memberQueryService.changePassword(me, request.oldPassword(), request.newPassword()); + @RequestBody @Valid MemberChangePasswordRequest request) { + memberCommandService.changePassword(me, request.oldPassword(), request.newPassword()); + return ResponseEntity.noContent().build(); + } + + @PatchMapping("/change-info") + public ResponseEntity changeMemberInfo(@MemberInterface Member me, + @RequestBody @Valid MemberChangeInfoRequest request){ + memberCommandService.changeDefaultInfo(me, request.name(), request.phoneNumber()); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/main/java/sw_css/member/application/MemberCommandService.java b/backend/src/main/java/sw_css/member/application/MemberCommandService.java new file mode 100644 index 00000000..7de906d5 --- /dev/null +++ b/backend/src/main/java/sw_css/member/application/MemberCommandService.java @@ -0,0 +1,36 @@ +package sw_css.member.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import sw_css.member.domain.Member; +import sw_css.member.domain.embedded.Password; +import sw_css.member.domain.repository.MemberRepository; +import sw_css.member.exception.MemberException; +import sw_css.member.exception.MemberExceptionType; + +@Service +@RequiredArgsConstructor +@Transactional +public class MemberCommandService { + + private final MemberRepository memberRepository; + + public void changePassword(Member me, String oldPassword, String newPassword) { + if (me.isWrongPassword(oldPassword)) { + throw new MemberException(MemberExceptionType.MEMBER_WRONG_PASSWORD); + } + + String encodedPassword = Password.encode(newPassword); + me.setPassword(encodedPassword); + + memberRepository.save(me); + } + + public void changeDefaultInfo(final Member me, final String name, final String phoneNumber) { + me.setName(name); + me.setPhoneNumber(phoneNumber); + + memberRepository.save(me); + } +} diff --git a/backend/src/main/java/sw_css/member/application/MemberQueryService.java b/backend/src/main/java/sw_css/member/application/MemberQueryService.java index e9bc7d56..4fddb02b 100644 --- a/backend/src/main/java/sw_css/member/application/MemberQueryService.java +++ b/backend/src/main/java/sw_css/member/application/MemberQueryService.java @@ -24,16 +24,5 @@ public StudentMemberResponse findStudentMember(final Long memberId) { .orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_STUDENT)); return StudentMemberResponse.from(student); } - - public void changePassword(Member me, String oldPassword, String newPassword) { - if (me.isWrongPassword(oldPassword)) { - throw new MemberException(MemberExceptionType.MEMBER_WRONG_PASSWORD); - } - - String encodedPassword = Password.encode(newPassword); - me.setPassword(encodedPassword); - - memberRepository.save(me); - } } diff --git a/backend/src/main/java/sw_css/member/application/dto/request/ChangePasswordRequest.java b/backend/src/main/java/sw_css/member/application/dto/request/ChangePasswordRequest.java deleted file mode 100644 index e319ddfc..00000000 --- a/backend/src/main/java/sw_css/member/application/dto/request/ChangePasswordRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package sw_css.member.application.dto.request; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; - -public record ChangePasswordRequest( - @NotNull(message = "이전 비밀번호 값을 입력해주세요.") - String oldPassword, - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*\\W)[A-Za-z\\d\\W]{6,15}$", message = "비밀번호는 6자에서 15자 이내의 영문, 숫자, 특수문자의 조합이어야 합니다.") - String newPassword) { - - public static ChangePasswordRequest from(String oldPassword, String newPassword) { - return new ChangePasswordRequest(oldPassword, newPassword); - } -} diff --git a/backend/src/main/java/sw_css/member/application/dto/request/MemberChangeInfoRequest.java b/backend/src/main/java/sw_css/member/application/dto/request/MemberChangeInfoRequest.java new file mode 100644 index 00000000..c1bad676 --- /dev/null +++ b/backend/src/main/java/sw_css/member/application/dto/request/MemberChangeInfoRequest.java @@ -0,0 +1,12 @@ +package sw_css.member.application.dto.request; + +import jakarta.validation.constraints.Pattern; +import sw_css.member.domain.embedded.PhoneNumber; +import sw_css.member.domain.embedded.RealName; + +public record MemberChangeInfoRequest( + @Pattern(regexp = RealName.NAME_REGEX, message = RealName.NAME_INVALID) + String name, + @Pattern(regexp = PhoneNumber.PHONE_NUMBER_REGEX, message = PhoneNumber.PHONE_NUMBER_INVALID) + String phoneNumber) { +} diff --git a/backend/src/main/java/sw_css/member/application/dto/request/MemberChangePasswordRequest.java b/backend/src/main/java/sw_css/member/application/dto/request/MemberChangePasswordRequest.java new file mode 100644 index 00000000..3680551d --- /dev/null +++ b/backend/src/main/java/sw_css/member/application/dto/request/MemberChangePasswordRequest.java @@ -0,0 +1,16 @@ +package sw_css.member.application.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import sw_css.member.domain.embedded.Password; + +public record MemberChangePasswordRequest( + @NotBlank(message = "이전 비밀번호 값을 입력해주세요.") + String oldPassword, + @Pattern(regexp = Password.PASSWORD_REGEX, message = Password.PASSWORD_INVALID) + String newPassword) { + + public static MemberChangePasswordRequest from(String oldPassword, String newPassword) { + return new MemberChangePasswordRequest(oldPassword, newPassword); + } +} diff --git a/backend/src/main/java/sw_css/member/domain/Member.java b/backend/src/main/java/sw_css/member/domain/Member.java index e1cc6a1f..2a99ee77 100644 --- a/backend/src/main/java/sw_css/member/domain/Member.java +++ b/backend/src/main/java/sw_css/member/domain/Member.java @@ -27,6 +27,7 @@ public class Member extends BaseEntity { @Column(nullable = false) private String email; + @Setter(AccessLevel.PUBLIC) @Column(nullable = false) private String name; @@ -34,6 +35,7 @@ public class Member extends BaseEntity { @Column(nullable = false) private String password; + @Setter(AccessLevel.PUBLIC) @Column(nullable = false) private String phoneNumber; diff --git a/backend/src/main/java/sw_css/member/exception/MemberExceptionType.java b/backend/src/main/java/sw_css/member/exception/MemberExceptionType.java index 8f25c5a9..bafbd011 100644 --- a/backend/src/main/java/sw_css/member/exception/MemberExceptionType.java +++ b/backend/src/main/java/sw_css/member/exception/MemberExceptionType.java @@ -5,7 +5,7 @@ public enum MemberExceptionType implements BaseExceptionType { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원을 찾을 수 없습니다."), - MEMBER_WRONG_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 잘못되었습니다."), + MEMBER_WRONG_PASSWORD(HttpStatus.BAD_REQUEST, "입력한 이전 비밀번호가 일치하지 않습니다."), NOT_FOUND_STUDENT(HttpStatus.NOT_FOUND, "해당하는 학생이 존재하지 않습니다."), INVALID_SEARCH_FIELD_ID(HttpStatus.BAD_REQUEST, "검색 유형이 올바르지 않습니다."), ; diff --git a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java index b828e236..db5d92ee 100644 --- a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java @@ -33,6 +33,7 @@ import sw_css.hackathon.application.HackathonTeamVoteQueryService; import sw_css.helper.ApiTestHelper; import sw_css.major.application.MajorQueryService; +import sw_css.member.application.MemberCommandService; import sw_css.member.application.MemberQueryService; import sw_css.member.domain.repository.MemberRepository; import sw_css.milestone.application.MilestoneHistoryCommandService; @@ -68,6 +69,9 @@ public abstract class RestDocsTest extends ApiTestHelper { @MockBean protected MemberQueryService memberQueryService; + @MockBean + protected MemberCommandService memberCommandService; + @MockBean protected MemberAdminQueryService memberAdminQueryService; diff --git a/backend/src/test/java/sw_css/restdocs/docs/MemberApiDocsTest.java b/backend/src/test/java/sw_css/restdocs/docs/MemberApiDocsTest.java index 2f51e396..0a1c8d83 100644 --- a/backend/src/test/java/sw_css/restdocs/docs/MemberApiDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/docs/MemberApiDocsTest.java @@ -22,7 +22,8 @@ import org.springframework.restdocs.payload.ResponseFieldsSnippet; import org.springframework.restdocs.request.PathParametersSnippet; import sw_css.member.api.MemberController; -import sw_css.member.application.dto.request.ChangePasswordRequest; +import sw_css.member.application.dto.request.MemberChangePasswordRequest; +import sw_css.member.application.dto.request.MemberChangeInfoRequest; import sw_css.member.application.dto.response.StudentMemberResponse; import sw_css.member.domain.Member; import sw_css.restdocs.RestDocsTest; @@ -77,10 +78,10 @@ public void changePassword() throws Exception { final String newPassword = "asdf1234!"; final String token = "Bearer AccessToken"; - final ChangePasswordRequest request = new ChangePasswordRequest(oldPassword, newPassword); + final MemberChangePasswordRequest request = new MemberChangePasswordRequest(oldPassword, newPassword); // when - doNothing().when(memberQueryService).changePassword(me, oldPassword, newPassword); + doNothing().when(memberCommandService).changePassword(me, oldPassword, newPassword); // then mockMvc.perform(RestDocumentationRequestBuilders.patch("/members/change-password") @@ -90,4 +91,30 @@ public void changePassword() throws Exception { .andExpect(status().isNoContent()) .andDo(document("member-change-password", requestFields)); } + + @Test + @DisplayName("[성공] 회원은 이름과 전화번호를 변경할 수 있다.") + public void changeDefaultInfo() throws Exception { + // given + final RequestFieldsSnippet requestFields = requestFields( + fieldWithPath("name").type(JsonFieldType.STRING).description("회원의 이름"), + fieldWithPath("phoneNumber").type(JsonFieldType.STRING).description("회원의 전화번호") + ); + + final Member me = new Member(1L, "ddang@pusan.ac.kr", "ddang", "qwer1234!", "01012341234"); + final String token = "Bearer AccessToken"; + + final MemberChangeInfoRequest request = new MemberChangeInfoRequest("이다은", "01031315656"); + + // when + doNothing().when(memberCommandService).changePassword(me, request.name(), request.phoneNumber()); + + // then + mockMvc.perform(RestDocumentationRequestBuilders.patch("/members/change-info") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)) + .header(HttpHeaders.AUTHORIZATION, token)) + .andExpect(status().isNoContent()) + .andDo(document("member-change-info", requestFields)); + } }