diff --git a/src/main/java/beforespring/socialfeed/member/controller/MemberController.java b/src/main/java/beforespring/socialfeed/member/controller/MemberController.java new file mode 100644 index 0000000..a16e8d2 --- /dev/null +++ b/src/main/java/beforespring/socialfeed/member/controller/MemberController.java @@ -0,0 +1,29 @@ +package beforespring.socialfeed.member.controller; + +import beforespring.socialfeed.member.controller.dto.CreateMemberDto; +import beforespring.socialfeed.member.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +@RestController +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + + /** + * 멤버 생성. 가입 요청과 가입 승인 서비스를 호출합니다. + * + * @param request 멤버 생성을 위한 dto + * @return 생성된 멤버의 아이디를 반환합니다. + */ + @PostMapping("/api/member/new") + public CreateMemberDto.Response createMember(@RequestBody @Valid CreateMemberDto.Request request) { + Long memberId = memberService.join(request); + return new CreateMemberDto.Response(memberId); + } +} diff --git a/src/main/java/beforespring/socialfeed/member/controller/dto/CreateMemberDto.java b/src/main/java/beforespring/socialfeed/member/controller/dto/CreateMemberDto.java new file mode 100644 index 0000000..30d5f5b --- /dev/null +++ b/src/main/java/beforespring/socialfeed/member/controller/dto/CreateMemberDto.java @@ -0,0 +1,91 @@ +package beforespring.socialfeed.member.controller.dto; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Size; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 멤버 생성 DTO + */ +public class CreateMemberDto { + + /** + * 멤버 생성 요청 dto. 이메일과 패스워드의 유효성을 검증함 + */ + @Getter + @NoArgsConstructor(access = AccessLevel.PROTECTED) + static public class Request { + @NotEmpty + private String username; + @NotEmpty + @Email(message = "이메일 형식이 올바르지 않습니다.") + private String email; + @NotEmpty + @Size(min = 10, message = "비밀번호는 최소 10자 이상이어야 합니다.") + private String password; + + public Request(String username, String email, String password) { + this.username = username; + this.email = email; + validatePasswordPattern(password); + this.password = password; + } + + /** + * 비밀번호 패턴 검증. 유효하지 않으면 에러를 던짐 + * + * @param password 검증할 패스워드 + */ + private void validatePasswordPattern(String password) { + if (isConsecutiveCharsPattern(password)) { + throw new IllegalArgumentException("동일한 문자를 3회 이상 연속으로 사용할 수 없습니다."); + } + if (!isComplexCharsPattern(password)) { + throw new IllegalArgumentException("숫자, 문자, 특수문자 중 2가지 이상을 포함해야 합니다."); + } + } + + /** + * 비밀번호 패턴 검사 로직 + * + * @param password 검증할 패스워드 + * @return 숫자, 문자, 특수문자가 중 2개 이상을 포함하면 true, 아니면 false + */ + private boolean isComplexCharsPattern(String password) { + String complexCharsPattern = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[^A-Za-z\\d]).{10,}$|" + + "(?=.*[A-Za-z])(?=.*\\d).{10,}$|" + + "(?=.*[A-Za-z])(?=.*[^A-Za-z\\d]).{10,}$|" + + "(?=.*\\d)(?=.*[^A-Za-z\\d]).{10,}$"; + Matcher matcher = Pattern.compile(complexCharsPattern).matcher(password); + return matcher.matches(); + } + + /** + * 비밀번호 패턴 검사 로직 + * + * @param password 검증할 패스워드 + * @return 동일한 문자가 3회 이상 연속되면 true, 아니면 false + */ + private boolean isConsecutiveCharsPattern(String password) { + String consecutiveCharsPattern = "(.)\\1\\1"; + Matcher matcher = Pattern.compile(consecutiveCharsPattern).matcher(password); + return matcher.find(); + } + } + + @Getter + @NoArgsConstructor(access = AccessLevel.PROTECTED) + static public class Response { + private Long id; + + public Response(Long id) { + this.id = id; + } + } +} diff --git a/src/main/java/beforespring/socialfeed/member/service/MemberService.java b/src/main/java/beforespring/socialfeed/member/service/MemberService.java index 502a98f..6e6a336 100644 --- a/src/main/java/beforespring/socialfeed/member/service/MemberService.java +++ b/src/main/java/beforespring/socialfeed/member/service/MemberService.java @@ -1,15 +1,18 @@ package beforespring.socialfeed.member.service; +import beforespring.socialfeed.member.controller.dto.CreateMemberDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + public interface MemberService { /** * 가입 요청. 가입 요청시 6자리의 랜덤 코드를 이메일로 발송. (이메일 발송 생략에 대해서 논의 필요) * - * @param username - * @param password - * @param email + * @param request 멤버 생성 요청 DTO + * @return member id */ - void join(String username, String password, String email); + Long join(CreateMemberDto.Request request); /** * 가입 승인 diff --git a/src/test/java/beforespring/socialfeed/member/controller/dto/CreateMemberDtoTest.java b/src/test/java/beforespring/socialfeed/member/controller/dto/CreateMemberDtoTest.java new file mode 100644 index 0000000..ae0cd8d --- /dev/null +++ b/src/test/java/beforespring/socialfeed/member/controller/dto/CreateMemberDtoTest.java @@ -0,0 +1,71 @@ +package beforespring.socialfeed.member.controller.dto; + +import beforespring.socialfeed.member.controller.dto.CreateMemberDto.Request; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CreateMemberDtoTest { + @Test + void password_validation_test() { + // given + String givenUsername = "givenUsername"; + String givenEmail = "givenEmail@gmail.com"; + String givenPassword = "passwdThatShou!dBe0kay"; + + // when then + assertThatCode( + () -> + new Request( + givenUsername, + givenEmail, + givenPassword + ) + ) + .describedAs("예외가 발생하지 않고 생성에 성공할것.") + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("3회 이상 연속되는 문자는 사용이 불가능합니다.") + void password_validation_passwd_not_valid_consecutive_character() { + // given + String givenUsername = "givenUsername"; + String givenEmail = "givenEmail@gmail.com"; + String givenPassword = "paaaaswd1!!"; // a가 3번 반복되는 잘못된 패스워드 + + // when then + assertThatThrownBy( + () -> + new Request( + givenUsername, + givenEmail, + givenPassword + ) + ) + .describedAs("3회 이상 반복되는 문자에 대해서 예외가 발생해야함.") + .hasMessageContaining("동일한 문자를 3회 이상 연속으로 사용할 수 없습니다."); + } + + @Test + @DisplayName("특수문자, 숫자, 문자 중 둘 이상은 포함해야함.") + void password_validation_passwd_not_valid_() { + // given + String givenUsername = "givenUsername"; + String givenEmail = "givenEmail@gmail.com"; + String givenPassword = "passwordNotOkay"; // 숫자와 특수문자가 없는 잘못된 패스워드 + // when then + assertThatThrownBy( + () -> + new Request( + givenUsername, + givenEmail, + givenPassword + ) + ) + .describedAs("숫자, 문자, 특수문자 중 2가지 이상을 포함해야함.") + .hasMessageContaining("숫자, 문자, 특수문자 중 2가지 이상을 포함해야 합니다."); + } +} \ No newline at end of file