From 2ba6338d70229582a2e4e2036b5b3069975d5bdc Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 20 Jul 2025 22:35:37 +0900 Subject: [PATCH 01/43] =?UTF-8?q?feat(#5):=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Member/controller/MemberController.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/MemberController.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/MemberController.java new file mode 100644 index 0000000..7514667 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/MemberController.java @@ -0,0 +1,27 @@ +package kr.ac.ks.cs_web_back.domain.Member.controller; + +import jakarta.validation.Valid; +import kr.ac.ks.cs_web_back.domain.Member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.Member.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController +@RequestMapping("/member") +@RequiredArgsConstructor +public class MemberController implements SpringDocMemberController { + + private final MemberService memberService; + + @PostMapping("/register") + public ResponseEntity createMember( + @Valid @RequestBody MemberCreateRequest request + ) { + memberService.createMember(request.email(), request.password(), request.username(), request.birthdate(), request.number()); + return ResponseEntity.ok().build(); + } +} From 14be12d4fd7be94ea76d6b3aff5fec617124430a Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 20 Jul 2025 22:36:30 +0900 Subject: [PATCH 02/43] =?UTF-8?q?docs(#5):=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SpringDocMemberController.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/SpringDocMemberController.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/SpringDocMemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/SpringDocMemberController.java new file mode 100644 index 0000000..4b605fb --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/SpringDocMemberController.java @@ -0,0 +1,32 @@ +package kr.ac.ks.cs_web_back.domain.Member.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.ac.ks.cs_web_back.domain.Member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.global.swagger.error.ApiErrorResponse; +import kr.ac.ks.cs_web_back.global.swagger.error.ErrorCase; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; + +@Tag(name = "멤버 API", description = "회원 관리 API") +public interface SpringDocMemberController { + + @Operation(summary = "회원가입", description = "회원 가입") + @ApiResponse(responseCode = "200", description = "회원가입에 성공했습니다.") + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/member/register", errorCases = { + @ErrorCase(description = "이메일 없음", exampleMessage = "이메일이 입력되지 않았습니다."), + @ErrorCase(description = "비밀번호 없음", exampleMessage = "비밀번호가 입력되지 않았습니다."), + @ErrorCase(description = "사용자명 없음", exampleMessage = "사용자명이 입력되지 않았습니다."), + @ErrorCase(description = "전화번호 없음", exampleMessage = "전화번호가 입력되지 않았습니다."), + @ErrorCase(description = "생년월일 없음", exampleMessage = "생년월일 입력되지 않았습니다.") + }) + @ApiErrorResponse(status = HttpStatus.CONFLICT, instance = "/member/register", errorCases = { + @ErrorCase(description = "중복 이메일", exampleMessage = "이미 존재하는 이메일입니다."), + @ErrorCase(description = "중복 사용자명", exampleMessage = "이미 존재하는 사용자명입니다.") + }) + ResponseEntity createMember( + @RequestBody MemberCreateRequest request + ); +} From 9ab329853034719461c1f73e4299ff567b4b98f4 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 20 Jul 2025 22:36:50 +0900 Subject: [PATCH 03/43] =?UTF-8?q?feat(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9A=94=EC=B2=AD=20=EB=B0=94=EB=94=94=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/MemberCreateRequest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/Member/dto/request/MemberCreateRequest.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/dto/request/MemberCreateRequest.java new file mode 100644 index 0000000..2db9236 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/dto/request/MemberCreateRequest.java @@ -0,0 +1,31 @@ +package kr.ac.ks.cs_web_back.domain.Member.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +@Schema(description = "회원가입") +public record MemberCreateRequest( + @Schema(description = "이메일", example = "example@ks.ac.kr") + @NotBlank(message = "이메일이 입력되지 않았습니다.") + String email, + + @Schema(description = "비밀번호", example = "examplePassword123") + @NotBlank(message = "비밀번호가 입력되지 않았습니다.") + @Size(max = 24, message = "비밀번호는 24자 내로 입력해 주세요.") + String password, + + @Schema(description = "사용자명", example = "userNickName") + @NotBlank(message = "사용자명이 입력되지 않았습니다.") + @Size(max = 12, message = "유저명은 12자 내로 입력해 주세요.") + String username, + + @Schema(description = "생년월일", example = "2002.09.05") + @NotBlank(message = "생년월일이 입력되지 않았습니다.") + String birthdate, + + @Schema(description = "전화번호", example = "055-663-5140") + @NotBlank(message = "전화번호가 입력되지 않았습니다.") + String number +) { +} From 812a6b9c6b40223128442a1ec3ce553360491e79 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 20 Jul 2025 22:59:38 +0900 Subject: [PATCH 04/43] =?UTF-8?q?feat(#5):=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/Member/model/Member.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/Member/model/Member.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/model/Member.java b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/model/Member.java new file mode 100644 index 0000000..5b3afda --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/model/Member.java @@ -0,0 +1,43 @@ +package kr.ac.ks.cs_web_back.domain.Member.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class Member { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true, nullable = false) + private String email; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private String username; + + @Column(nullable = false) + private String birthdate; + + @Column(nullable = false) + private String number; + + private Member(String email, String password, String username, String birthdate, String number) { + this.email = email; + this.password = password; + this.username = username; + this.birthdate = birthdate; + this.number = number; + } + + public static Member of(String email, String password, String username, String birthdate, String number) { + return new Member(email, password, username, birthdate, number); + } +} From c66ab465112ad6bb47faf165447ab53a913a4d03 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 20 Jul 2025 22:59:54 +0900 Subject: [PATCH 05/43] =?UTF-8?q?feat(#5):=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/Member/repository/MemberRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/Member/repository/MemberRepository.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/repository/MemberRepository.java b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/repository/MemberRepository.java new file mode 100644 index 0000000..9b4c59c --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/repository/MemberRepository.java @@ -0,0 +1,7 @@ +package kr.ac.ks.cs_web_back.domain.Member.repository; + +import kr.ac.ks.cs_web_back.domain.Member.model.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { +} From fb4d17eaace2ac5dc36605228a206b38b76b8c66 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 20 Jul 2025 23:00:09 +0900 Subject: [PATCH 06/43] =?UTF-8?q?feat(#5):=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/Member/service/MemberService.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/Member/service/MemberService.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/service/MemberService.java new file mode 100644 index 0000000..9a8c693 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/Member/service/MemberService.java @@ -0,0 +1,20 @@ +package kr.ac.ks.cs_web_back.domain.Member.service; + +import kr.ac.ks.cs_web_back.domain.Member.model.Member; +import kr.ac.ks.cs_web_back.domain.Member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class MemberService { + + private final MemberRepository memberRepository; + + public void createMember(String email, String password, String username, String birthdate, String number) { + Member member = Member.of(email, password, username, birthdate, number); + memberRepository.save(member); + } +} From 6e225965627afd618b77f49263be770a22afea75 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 18:32:42 +0900 Subject: [PATCH 07/43] =?UTF-8?q?test(#4):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberControllerTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java new file mode 100644 index 0000000..f80f9c4 --- /dev/null +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java @@ -0,0 +1,49 @@ +package kr.ac.ks.cs_web_back.domain.member.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.ac.ks.cs_web_back.domain.Member.controller.MemberController; +import kr.ac.ks.cs_web_back.domain.Member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.Member.service.MemberService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = MemberController.class) +public class MemberControllerTest { + + @MockBean + private MemberService memberService; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("회원가입에 성공하면 200 Ok를 반환한다.") + public void RegisteredSuccessfullyReturns200Ok() throws Exception { + // given + MemberCreateRequest request = new MemberCreateRequest( + "validEmail123@ks.ac.kr", + "validPassword1234", + "exampleUsername", + "2002.09.05", + "010-1234-5678" + ); + + // when & then + mockMvc.perform(post("/member/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()); + } + +} From e2ca64108c280e281e6fbe7ff92f6849f735af74 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 18:47:48 +0900 Subject: [PATCH 08/43] =?UTF-8?q?refactor(#5):=20Member=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/{Member => member}/controller/MemberController.java | 0 .../{Member => member}/controller/SpringDocMemberController.java | 0 .../{Member => member}/dto/request/MemberCreateRequest.java | 0 .../ac/ks/cs_web_back/domain/{Member => member}/model/Member.java | 0 .../domain/{Member => member}/repository/MemberRepository.java | 0 .../domain/{Member => member}/service/MemberService.java | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename src/main/java/kr/ac/ks/cs_web_back/domain/{Member => member}/controller/MemberController.java (100%) rename src/main/java/kr/ac/ks/cs_web_back/domain/{Member => member}/controller/SpringDocMemberController.java (100%) rename src/main/java/kr/ac/ks/cs_web_back/domain/{Member => member}/dto/request/MemberCreateRequest.java (100%) rename src/main/java/kr/ac/ks/cs_web_back/domain/{Member => member}/model/Member.java (100%) rename src/main/java/kr/ac/ks/cs_web_back/domain/{Member => member}/repository/MemberRepository.java (100%) rename src/main/java/kr/ac/ks/cs_web_back/domain/{Member => member}/service/MemberService.java (100%) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java similarity index 100% rename from src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/MemberController.java rename to src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/SpringDocMemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java similarity index 100% rename from src/main/java/kr/ac/ks/cs_web_back/domain/Member/controller/SpringDocMemberController.java rename to src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java similarity index 100% rename from src/main/java/kr/ac/ks/cs_web_back/domain/Member/dto/request/MemberCreateRequest.java rename to src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/model/Member.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java similarity index 100% rename from src/main/java/kr/ac/ks/cs_web_back/domain/Member/model/Member.java rename to src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/repository/MemberRepository.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java similarity index 100% rename from src/main/java/kr/ac/ks/cs_web_back/domain/Member/repository/MemberRepository.java rename to src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/Member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java similarity index 100% rename from src/main/java/kr/ac/ks/cs_web_back/domain/Member/service/MemberService.java rename to src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java From d77833ac6c322bec77e1a6c634f9a6bc7c776146 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 18:48:15 +0900 Subject: [PATCH 09/43] =?UTF-8?q?feat(#5):=20=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EB=B6=80=EB=AA=A8=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs_web_back/domain/common/BaseEntity.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/common/BaseEntity.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/common/BaseEntity.java b/src/main/java/kr/ac/ks/cs_web_back/domain/common/BaseEntity.java new file mode 100644 index 0000000..bcc5a4e --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/common/BaseEntity.java @@ -0,0 +1,23 @@ +package kr.ac.ks.cs_web_back.domain.common; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity { + + @CreatedDate + private LocalDateTime generatedAt; + + @LastModifiedDate + private LocalDateTime modifiedAt; + +} From 4a7b2d4dbdba35a9b8b18589dc661f40374286a6 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 18:48:51 +0900 Subject: [PATCH 10/43] =?UTF-8?q?feat(#5):=20BaseEntity=20=EC=83=81?= =?UTF-8?q?=EC=86=8D=20=EB=B0=8F=20birthdate=20LocalDate=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ks/cs_web_back/domain/member/model/Member.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java index 5b3afda..0a12c6c 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java @@ -1,15 +1,18 @@ -package kr.ac.ks.cs_web_back.domain.Member.model; +package kr.ac.ks.cs_web_back.domain.member.model; import jakarta.persistence.*; +import kr.ac.ks.cs_web_back.domain.common.BaseEntity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; + @Entity @Getter @NoArgsConstructor @AllArgsConstructor -public class Member { +public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -24,12 +27,12 @@ public class Member { private String username; @Column(nullable = false) - private String birthdate; + private LocalDate birthdate; @Column(nullable = false) private String number; - private Member(String email, String password, String username, String birthdate, String number) { + private Member(String email, String password, String username, LocalDate birthdate, String number) { this.email = email; this.password = password; this.username = username; @@ -37,7 +40,7 @@ private Member(String email, String password, String username, String birthdate this.number = number; } - public static Member of(String email, String password, String username, String birthdate, String number) { + public static Member of(String email, String password, String username, LocalDate birthdate, String number) { return new Member(email, password, username, birthdate, number); } } From ad492b5a22c0fbc8d96b85fe47766189a670738e Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 18:49:35 +0900 Subject: [PATCH 11/43] =?UTF-8?q?refactor(#5):=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EB=AA=85=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20import=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberController.java | 6 +++--- .../domain/member/controller/SpringDocMemberController.java | 4 ++-- .../domain/member/dto/request/MemberCreateRequest.java | 2 +- .../domain/member/repository/MemberRepository.java | 4 ++-- .../ks/cs_web_back/domain/member/service/MemberService.java | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java index 7514667..a640dcf 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java @@ -1,8 +1,8 @@ -package kr.ac.ks.cs_web_back.domain.Member.controller; +package kr.ac.ks.cs_web_back.domain.member.controller; import jakarta.validation.Valid; -import kr.ac.ks.cs_web_back.domain.Member.dto.request.MemberCreateRequest; -import kr.ac.ks.cs_web_back.domain.Member.service.MemberService; +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.member.service.MemberService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java index 4b605fb..fc8cf33 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java @@ -1,9 +1,9 @@ -package kr.ac.ks.cs_web_back.domain.Member.controller; +package kr.ac.ks.cs_web_back.domain.member.controller; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import kr.ac.ks.cs_web_back.domain.Member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; import kr.ac.ks.cs_web_back.global.swagger.error.ApiErrorResponse; import kr.ac.ks.cs_web_back.global.swagger.error.ErrorCase; import org.springframework.http.HttpStatus; diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java index 2db9236..0d0d228 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java @@ -1,4 +1,4 @@ -package kr.ac.ks.cs_web_back.domain.Member.dto.request; +package kr.ac.ks.cs_web_back.domain.member.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java index 9b4c59c..ade972c 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java @@ -1,6 +1,6 @@ -package kr.ac.ks.cs_web_back.domain.Member.repository; +package kr.ac.ks.cs_web_back.domain.member.repository; -import kr.ac.ks.cs_web_back.domain.Member.model.Member; +import kr.ac.ks.cs_web_back.domain.member.model.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java index 9a8c693..58b413c 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java @@ -1,7 +1,7 @@ -package kr.ac.ks.cs_web_back.domain.Member.service; +package kr.ac.ks.cs_web_back.domain.member.service; -import kr.ac.ks.cs_web_back.domain.Member.model.Member; -import kr.ac.ks.cs_web_back.domain.Member.repository.MemberRepository; +import kr.ac.ks.cs_web_back.domain.member.model.Member; +import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; From 1c784c9ac94eec4aeed0a1f5666a628af8ab26e7 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 18:59:48 +0900 Subject: [PATCH 12/43] =?UTF-8?q?feat(#5):=20birthdate=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/dto/request/MemberCreateRequest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java index 0d0d228..41fb5f1 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java @@ -4,6 +4,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.time.LocalDate; + @Schema(description = "회원가입") public record MemberCreateRequest( @Schema(description = "이메일", example = "example@ks.ac.kr") @@ -20,9 +22,9 @@ public record MemberCreateRequest( @Size(max = 12, message = "유저명은 12자 내로 입력해 주세요.") String username, - @Schema(description = "생년월일", example = "2002.09.05") + @Schema(description = "생년월일", example = "2002-09-05") @NotBlank(message = "생년월일이 입력되지 않았습니다.") - String birthdate, + LocalDate birthdate, @Schema(description = "전화번호", example = "055-663-5140") @NotBlank(message = "전화번호가 입력되지 않았습니다.") From b3c7df12a87bbf6e2e9f0464d75dc5be4f0ca54b Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 19:00:30 +0900 Subject: [PATCH 13/43] =?UTF-8?q?feat(#5):=20=EB=A9=A4=EB=B2=84=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=ED=8C=A9=ED=86=A0=EB=A6=AC=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B8=EC=9E=90=EB=A5=BC=20dto=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberController.java | 2 +- .../ac/ks/cs_web_back/domain/member/model/Member.java | 11 +++++++++-- .../domain/member/service/MemberService.java | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java index a640dcf..bd334a1 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java @@ -21,7 +21,7 @@ public class MemberController implements SpringDocMemberController { public ResponseEntity createMember( @Valid @RequestBody MemberCreateRequest request ) { - memberService.createMember(request.email(), request.password(), request.username(), request.birthdate(), request.number()); + memberService.createMember(request); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java index 0a12c6c..44331bf 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java @@ -2,6 +2,7 @@ import jakarta.persistence.*; import kr.ac.ks.cs_web_back.domain.common.BaseEntity; +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -40,7 +41,13 @@ private Member(String email, String password, String username, LocalDate birthd this.number = number; } - public static Member of(String email, String password, String username, LocalDate birthdate, String number) { - return new Member(email, password, username, birthdate, number); + public static Member of(MemberCreateRequest request) { + return new Member( + request.email(), + request.password(), + request.username(), + request.birthdate(), + request.number() + ); } } diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java index 58b413c..818be0e 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java @@ -1,5 +1,6 @@ package kr.ac.ks.cs_web_back.domain.member.service; +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; import kr.ac.ks.cs_web_back.domain.member.model.Member; import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; @@ -13,8 +14,8 @@ public class MemberService { private final MemberRepository memberRepository; - public void createMember(String email, String password, String username, String birthdate, String number) { - Member member = Member.of(email, password, username, birthdate, number); + public void createMember(MemberCreateRequest request) { + Member member = Member.of(request); memberRepository.save(member); } } From 8e731327b69fd44cb5d43b275d52924b6aea0044 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 19:05:10 +0900 Subject: [PATCH 14/43] =?UTF-8?q?test(#5):=20=EB=A9=A4=EB=B2=84=20fixture?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/fixture/MemberFixture.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java b/src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java new file mode 100644 index 0000000..13deb3e --- /dev/null +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java @@ -0,0 +1,21 @@ +package kr.ac.ks.cs_web_back.domain.member.fixture; + +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.member.model.Member; + +import java.time.LocalDate; + +public class MemberFixture { + + public static Member memberFixture() { + MemberCreateRequest request = new MemberCreateRequest( + "example@ks.ac.kr", + "password123", + "username", + LocalDate.parse("2002-09-05"), + "010-1234-5678" + ); + return Member.of(request); + } + +} From 831e3d79cbccd27181023f551962afbd0fd352da Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 26 Jul 2025 19:05:26 +0900 Subject: [PATCH 15/43] =?UTF-8?q?test(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9A=94=EC=B2=AD=20dto=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberControllerTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java index f80f9c4..75d0193 100644 --- a/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java @@ -1,9 +1,8 @@ package kr.ac.ks.cs_web_back.domain.member.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import kr.ac.ks.cs_web_back.domain.Member.controller.MemberController; -import kr.ac.ks.cs_web_back.domain.Member.dto.request.MemberCreateRequest; -import kr.ac.ks.cs_web_back.domain.Member.service.MemberService; +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.member.service.MemberService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -12,6 +11,8 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import java.time.LocalDate; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -35,7 +36,7 @@ public void RegisteredSuccessfullyReturns200Ok() throws Exception { "validEmail123@ks.ac.kr", "validPassword1234", "exampleUsername", - "2002.09.05", + LocalDate.parse("2002-09-05"), "010-1234-5678" ); From 22c2247403c32152aa1454f5efff1dd92c2297a3 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 15:32:41 +0900 Subject: [PATCH 16/43] =?UTF-8?q?refactor(#5):=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs_web_back/domain/member/controller/MemberController.java | 2 +- .../domain/member/controller/SpringDocMemberController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java index bd334a1..c359926 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java @@ -18,7 +18,7 @@ public class MemberController implements SpringDocMemberController { private final MemberService memberService; @PostMapping("/register") - public ResponseEntity createMember( + public ResponseEntity register( @Valid @RequestBody MemberCreateRequest request ) { memberService.createMember(request); diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java index fc8cf33..3b41060 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java @@ -26,7 +26,7 @@ public interface SpringDocMemberController { @ErrorCase(description = "중복 이메일", exampleMessage = "이미 존재하는 이메일입니다."), @ErrorCase(description = "중복 사용자명", exampleMessage = "이미 존재하는 사용자명입니다.") }) - ResponseEntity createMember( + ResponseEntity register( @RequestBody MemberCreateRequest request ); } From 0e7423394879f85a081fd9ec19b01b07e8cd4352 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 15:33:03 +0900 Subject: [PATCH 17/43] =?UTF-8?q?chore(#5):=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=95=B4=EC=8B=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index bdd3847..88d6e69 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' From fc16c49242284cae75e4a51dff7c57f94f1ba821 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 16:17:05 +0900 Subject: [PATCH 18/43] =?UTF-8?q?feat(#5):=20=EB=B3=B4=EC=95=88=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20SecurityConfig=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs_web_back/global/config/JpaConfig.java | 5 +++ .../global/config/SecurityConfig.java | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/config/JpaConfig.java b/src/main/java/kr/ac/ks/cs_web_back/global/config/JpaConfig.java index 34ceef5..48c47f1 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/config/JpaConfig.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/config/JpaConfig.java @@ -1,4 +1,9 @@ package kr.ac.ks.cs_web_back.global.config; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing public class JpaConfig { } diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java b/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java new file mode 100644 index 0000000..347e02b --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java @@ -0,0 +1,33 @@ +package kr.ac.ks.cs_web_back.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authz -> authz + .requestMatchers("/member/register", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .anyRequest().authenticated() + ); + + return http.build(); + } +} From ba3eef88510acf7d50f09a605fcc31d05da392d9 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 16:18:16 +0900 Subject: [PATCH 19/43] =?UTF-8?q?feat(#5):=20Member=20=EC=84=B1=EA=B3=B5,?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/code/MemberExceptionCode.java | 20 ++++++++++++++++++ .../controller/code/MemberSuccessCode.java | 21 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java create mode 100644 src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java new file mode 100644 index 0000000..1ac9747 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberExceptionCode.java @@ -0,0 +1,20 @@ +package kr.ac.ks.cs_web_back.domain.member.controller.code; + +import kr.ac.ks.cs_web_back.global.exeption.dto.ExceptionCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MemberExceptionCode implements ExceptionCode { + + BAD_REQUEST_NO_EMAIL(9010, "이메일이 입력되지 않았습니다."), + BAD_REQUEST_NO_PASSWORD(9011, "비밀번호가 입력되지 않았습니다."), + BAD_REQUEST_NO_USERNAME(9012, "사용자명이 입력되지 않았습니다."), + CONFLICT_EMAIL(5010, "이미 존재하는 이메일입니다."), + CONFLICT_USERNAME(5012, "이미 존재하는 사용자명입니다."), + ; + + private final int code; + private final String message; +} diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java new file mode 100644 index 0000000..1c7cab9 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/code/MemberSuccessCode.java @@ -0,0 +1,21 @@ +package kr.ac.ks.cs_web_back.domain.member.controller.code; + +import kr.ac.ks.cs_web_back.global.response.SuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MemberSuccessCode implements SuccessCode { + + GENERATED_REGISTERED(2101, "회원가입에 성공했습니다."), + OK_WITHDRAWN(2102,"회원 탈퇴에 성공했습니다."), + OK_FOUND_USER_PROFILE(2103, "사용자 정보 조회에 성공했습니다."), + OK_MODIFIED_USER_PROFILE(2104, "사용자 정보 변경에 성공했습니다."), + OK_FOUND_USER_WRITTEN_POSTS(2105, "해당 사용자가 작성한 게시글을 조회했습니다."), + OK_FOUND_USER_WRITTEN_COMMENTS(2106, "해당 사용자가 작성한 댓글을 조회했습니다."), + ; + + private final int code; + private final String message; +} From c4c7dbbbce71f41a7664b073098c93ac1447d5c2 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 16:19:05 +0900 Subject: [PATCH 20/43] =?UTF-8?q?feat(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=83=9D=EB=85=84=EC=9B=94=EC=9D=BC,?= =?UTF-8?q?=20=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=82=AC=ED=95=AD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(Nullable)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/MemberCreateRequest.java | 5 +++-- .../domain/member/model/Member.java | 22 +++++-------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java index 41fb5f1..9a54e74 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java @@ -1,6 +1,7 @@ package kr.ac.ks.cs_web_back.domain.member.dto.request; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @@ -23,11 +24,11 @@ public record MemberCreateRequest( String username, @Schema(description = "생년월일", example = "2002-09-05") - @NotBlank(message = "생년월일이 입력되지 않았습니다.") + @Nullable LocalDate birthdate, @Schema(description = "전화번호", example = "055-663-5140") - @NotBlank(message = "전화번호가 입력되지 않았습니다.") + @Nullable String number ) { } diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java index 44331bf..ce27c0a 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/model/Member.java @@ -2,8 +2,8 @@ import jakarta.persistence.*; import kr.ac.ks.cs_web_back.domain.common.BaseEntity; -import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; -import lombok.AllArgsConstructor; +import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,8 +11,7 @@ @Entity @Getter -@NoArgsConstructor -@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -27,12 +26,13 @@ public class Member extends BaseEntity { @Column(nullable = false) private String username; - @Column(nullable = false) + @Column private LocalDate birthdate; - @Column(nullable = false) + @Column private String number; + @Builder private Member(String email, String password, String username, LocalDate birthdate, String number) { this.email = email; this.password = password; @@ -40,14 +40,4 @@ private Member(String email, String password, String username, LocalDate birthd this.birthdate = birthdate; this.number = number; } - - public static Member of(MemberCreateRequest request) { - return new Member( - request.email(), - request.password(), - request.username(), - request.birthdate(), - request.number() - ); - } } From 081d6e5a16fb348e45e8061138c42239ffc0cc9d Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 16:32:41 +0900 Subject: [PATCH 21/43] =?UTF-8?q?feat(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=A6=AC?= =?UTF-8?q?=ED=84=B4=20=ED=83=80=EC=9E=85=20CsResponse=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberController.java | 11 ++++++++--- .../member/controller/SpringDocMemberController.java | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java index c359926..f962b11 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java @@ -1,14 +1,19 @@ package kr.ac.ks.cs_web_back.domain.member.controller; import jakarta.validation.Valid; +import kr.ac.ks.cs_web_back.domain.member.controller.code.MemberSuccessCode; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; import kr.ac.ks.cs_web_back.domain.member.service.MemberService; +import kr.ac.ks.cs_web_back.global.response.CsResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; 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.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; @RestController @RequestMapping("/member") @@ -18,10 +23,10 @@ public class MemberController implements SpringDocMemberController { private final MemberService memberService; @PostMapping("/register") - public ResponseEntity register( + public CsResponse register( @Valid @RequestBody MemberCreateRequest request ) { - memberService.createMember(request); - return ResponseEntity.ok().build(); + Long id = memberService.createMember(request); + return CsResponse.of(MemberSuccessCode.GENERATED_REGISTERED, id); } } diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java index 3b41060..d531e61 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.global.response.CsResponse; import kr.ac.ks.cs_web_back.global.swagger.error.ApiErrorResponse; import kr.ac.ks.cs_web_back.global.swagger.error.ErrorCase; import org.springframework.http.HttpStatus; @@ -26,7 +27,7 @@ public interface SpringDocMemberController { @ErrorCase(description = "중복 이메일", exampleMessage = "이미 존재하는 이메일입니다."), @ErrorCase(description = "중복 사용자명", exampleMessage = "이미 존재하는 사용자명입니다.") }) - ResponseEntity register( + CsResponse register( @RequestBody MemberCreateRequest request ); } From a188e588cadcc5f470eeec83c7d78997670b1a88 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 16:40:31 +0900 Subject: [PATCH 22/43] =?UTF-8?q?feat(#5):=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC/=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=ED=8C=A9=ED=86=A0=EB=A6=AC=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/repository/MemberRepository.java | 4 ++- .../domain/member/service/MemberService.java | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java index ade972c..3e5c7f9 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/repository/MemberRepository.java @@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { -} + boolean existsByEmail(String email); + boolean existsByUsername(String username); +} \ No newline at end of file diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java index 818be0e..c214e7d 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java @@ -1,9 +1,13 @@ package kr.ac.ks.cs_web_back.domain.member.service; +import kr.ac.ks.cs_web_back.domain.member.controller.code.MemberExceptionCode; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; import kr.ac.ks.cs_web_back.domain.member.model.Member; import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; +import kr.ac.ks.cs_web_back.global.exeption.domain.ConflictException; +import kr.ac.ks.cs_web_back.global.response.SuccessCode; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,9 +17,25 @@ public class MemberService { private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; - public void createMember(MemberCreateRequest request) { - Member member = Member.of(request); - memberRepository.save(member); + public Long createMember(MemberCreateRequest request) { + if (memberRepository.existsByEmail(request.email())) + throw new ConflictException(MemberExceptionCode.CONFLICT_EMAIL); + + if (memberRepository.existsByUsername(request.username())) + throw new ConflictException(MemberExceptionCode.CONFLICT_USERNAME); + + String encodedPassword = passwordEncoder.encode(request.password()); + + Member member = Member.builder() + .email(request.email()) + .password(encodedPassword) + .username(request.username()) + .birthdate(request.birthdate()) + .number(request.number()) + .build(); + + return memberRepository.save(member).getId(); } } From 0af478a3b1697129d0b6e82ebbbc14907a1928c8 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 16:54:30 +0900 Subject: [PATCH 23/43] =?UTF-8?q?chore(#5):=20=EC=8A=A4=EC=9B=A8=EA=B1=B0?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=EC=9D=91=EB=8B=B5=20=EC=98=88=EC=8B=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swagger/error/ApiErrorResponsesCustomizer.java | 2 +- .../ks/cs_web_back/global/swagger/error/ErrorCase.java | 1 + .../global/swagger/error/ProblemDetailSchema.java | 10 +++------- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ApiErrorResponsesCustomizer.java b/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ApiErrorResponsesCustomizer.java index e6297b2..2529616 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ApiErrorResponsesCustomizer.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ApiErrorResponsesCustomizer.java @@ -79,6 +79,6 @@ private MediaType makeMediaType(ApiErrorResponse apiErrorResponse) { private Example makeExample(ApiErrorResponse apiErrorResponse, ErrorCase errorCase) { return new Example() .summary(errorCase.description()) - .value(ProblemDetailSchema.of(apiErrorResponse, errorCase.exampleMessage())); + .value(ProblemDetailSchema.of(apiErrorResponse, errorCase.code(), errorCase.exampleMessage())); } } diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ErrorCase.java b/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ErrorCase.java index 36975cf..ba5911a 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ErrorCase.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ErrorCase.java @@ -10,5 +10,6 @@ @Retention(RetentionPolicy.RUNTIME) public @interface ErrorCase { String description(); + int code(); String exampleMessage(); } diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ProblemDetailSchema.java b/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ProblemDetailSchema.java index d828e39..32662f3 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ProblemDetailSchema.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/swagger/error/ProblemDetailSchema.java @@ -1,17 +1,13 @@ package kr.ac.ks.cs_web_back.global.swagger.error; public record ProblemDetailSchema( - String type, - String title, - int status, + int code, String detail, String instance ) { - public static ProblemDetailSchema of(ApiErrorResponse apiErrorResponse, String detail) { + public static ProblemDetailSchema of(ApiErrorResponse apiErrorResponse, int code, String detail) { return new ProblemDetailSchema( - apiErrorResponse.type(), - apiErrorResponse.status().name(), - apiErrorResponse.status().value(), + code, detail, apiErrorResponse.instance() ); From 5fed50cdf854c0a1bd0106466ce9f5487c94bbbd Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 16:55:25 +0900 Subject: [PATCH 24/43] =?UTF-8?q?docs(#5):=20=EC=98=A4=EB=A5=98=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=98=88=EC=8B=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SpringDocMemberController.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java index d531e61..7d9c3fe 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java @@ -15,17 +15,15 @@ public interface SpringDocMemberController { @Operation(summary = "회원가입", description = "회원 가입") - @ApiResponse(responseCode = "200", description = "회원가입에 성공했습니다.") + @ApiResponse(responseCode = "201", description = "회원가입에 성공했습니다.") @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/member/register", errorCases = { - @ErrorCase(description = "이메일 없음", exampleMessage = "이메일이 입력되지 않았습니다."), - @ErrorCase(description = "비밀번호 없음", exampleMessage = "비밀번호가 입력되지 않았습니다."), - @ErrorCase(description = "사용자명 없음", exampleMessage = "사용자명이 입력되지 않았습니다."), - @ErrorCase(description = "전화번호 없음", exampleMessage = "전화번호가 입력되지 않았습니다."), - @ErrorCase(description = "생년월일 없음", exampleMessage = "생년월일 입력되지 않았습니다.") + @ErrorCase(description = "이메일 없음", code = 9010, exampleMessage = "이메일이 입력되지 않았습니다."), + @ErrorCase(description = "비밀번호 없음", code = 9011, exampleMessage = "비밀번호가 입력되지 않았습니다."), + @ErrorCase(description = "사용자명 없음", code = 9012, exampleMessage = "사용자명이 입력되지 않았습니다.") }) @ApiErrorResponse(status = HttpStatus.CONFLICT, instance = "/member/register", errorCases = { - @ErrorCase(description = "중복 이메일", exampleMessage = "이미 존재하는 이메일입니다."), - @ErrorCase(description = "중복 사용자명", exampleMessage = "이미 존재하는 사용자명입니다.") + @ErrorCase(description = "중복 이메일", code = 5010, exampleMessage = "이미 존재하는 이메일입니다."), + @ErrorCase(description = "중복 사용자명", code = 5011, exampleMessage = "이미 존재하는 사용자명입니다.") }) CsResponse register( @RequestBody MemberCreateRequest request From c150b3a1902a2ce5dc5c693a9eb4f6568cf9de21 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 17:08:00 +0900 Subject: [PATCH 25/43] =?UTF-8?q?feat(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=83=9D=EB=85=84=EC=9B=94=EC=9D=BC/?= =?UTF-8?q?=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/dto/request/MemberCreateRequest.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java index 9a54e74..d9ec564 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java @@ -21,14 +21,6 @@ public record MemberCreateRequest( @Schema(description = "사용자명", example = "userNickName") @NotBlank(message = "사용자명이 입력되지 않았습니다.") @Size(max = 12, message = "유저명은 12자 내로 입력해 주세요.") - String username, - - @Schema(description = "생년월일", example = "2002-09-05") - @Nullable - LocalDate birthdate, - - @Schema(description = "전화번호", example = "055-663-5140") - @Nullable - String number + String username ) { } From 414ea2ebc7c71d28ef3c8d35f86b47feb139a976 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 18:33:36 +0900 Subject: [PATCH 26/43] =?UTF-8?q?feat(#5):=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberController.java | 11 +++-------- .../member/controller/SpringDocMemberController.java | 1 - .../member/dto/request/MemberCreateRequest.java | 3 --- .../domain/member/service/MemberService.java | 3 --- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java index f962b11..ce1a57e 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberController.java @@ -6,14 +6,8 @@ import kr.ac.ks.cs_web_back.domain.member.service.MemberService; import kr.ac.ks.cs_web_back.global.response.CsResponse; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -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.RestController; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - -import java.net.URI; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/member") @@ -23,6 +17,7 @@ public class MemberController implements SpringDocMemberController { private final MemberService memberService; @PostMapping("/register") + @ResponseStatus(HttpStatus.CREATED) public CsResponse register( @Valid @RequestBody MemberCreateRequest request ) { diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java index 7d9c3fe..1f5fd2e 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/controller/SpringDocMemberController.java @@ -8,7 +8,6 @@ import kr.ac.ks.cs_web_back.global.swagger.error.ApiErrorResponse; import kr.ac.ks.cs_web_back.global.swagger.error.ErrorCase; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; @Tag(name = "멤버 API", description = "회원 관리 API") diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java index d9ec564..8537494 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java @@ -1,12 +1,9 @@ package kr.ac.ks.cs_web_back.domain.member.dto.request; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -import java.time.LocalDate; - @Schema(description = "회원가입") public record MemberCreateRequest( @Schema(description = "이메일", example = "example@ks.ac.kr") diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java index c214e7d..a8b60d6 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/service/MemberService.java @@ -5,7 +5,6 @@ import kr.ac.ks.cs_web_back.domain.member.model.Member; import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; import kr.ac.ks.cs_web_back.global.exeption.domain.ConflictException; -import kr.ac.ks.cs_web_back.global.response.SuccessCode; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -32,8 +31,6 @@ public Long createMember(MemberCreateRequest request) { .email(request.email()) .password(encodedPassword) .username(request.username()) - .birthdate(request.birthdate()) - .number(request.number()) .build(); return memberRepository.save(member).getId(); From 98bcf631cc762d6c672915e09912a9347890264d Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 18:46:59 +0900 Subject: [PATCH 27/43] =?UTF-8?q?chore(#5):=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 88d6e69..943d369 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' compileOnly 'org.projectlombok:lombok' From 26c5bcaca8121c04f91e8d4d361813031dcede1e Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 18:47:33 +0900 Subject: [PATCH 28/43] =?UTF-8?q?feat(#5):=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=ED=98=95=EC=8B=9D=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/dto/request/MemberCreateRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java index 8537494..2b1ad84 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java @@ -1,13 +1,13 @@ package kr.ac.ks.cs_web_back.domain.member.dto.request; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.*; @Schema(description = "회원가입") public record MemberCreateRequest( @Schema(description = "이메일", example = "example@ks.ac.kr") @NotBlank(message = "이메일이 입력되지 않았습니다.") + @Email(message = "이메일 형식으로 입력해 주세요.") String email, @Schema(description = "비밀번호", example = "examplePassword123") From 364454d8dc5fabcef59feb5db5f6b7f2a7e34bbc Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 19:14:40 +0900 Subject: [PATCH 29/43] =?UTF-8?q?feat(#5):=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=82=AC=20=EC=98=88=EC=99=B8=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exeption/GlobalExceptionHandler.java | 28 ++++++++++++++++ .../exeption/dto/ValidationExceptionCode.java | 33 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/main/java/kr/ac/ks/cs_web_back/global/exeption/dto/ValidationExceptionCode.java diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/exeption/GlobalExceptionHandler.java b/src/main/java/kr/ac/ks/cs_web_back/global/exeption/GlobalExceptionHandler.java index 602cebb..d86aa75 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/exeption/GlobalExceptionHandler.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/exeption/GlobalExceptionHandler.java @@ -3,10 +3,17 @@ import kr.ac.ks.cs_web_back.global.exeption.domain.*; import kr.ac.ks.cs_web_back.global.exeption.dto.ExceptionCode; import kr.ac.ks.cs_web_back.global.exeption.dto.ExceptionResponse; +import kr.ac.ks.cs_web_back.global.exeption.dto.ValidationExceptionCode; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +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 kr.ac.ks.cs_web_back.global.exeption.dto.ValidationExceptionCode; +import org.springframework.validation.FieldError; + +import java.util.List; @RestControllerAdvice public class GlobalExceptionHandler { @@ -28,6 +35,27 @@ public ResponseEntity handleException(final BadRequestExcepti .body(new ExceptionResponse(exception.getCode(), exception.getMessage())); } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleException(final MethodArgumentNotValidException e) { + System.out.printf("%s : %s\n", e.getClass(), e.getMessage()); + + final FieldError fieldError = e.getBindingResult().getFieldError(); + + if (fieldError == null) { + return ResponseEntity.badRequest() + .body(new ExceptionResponse(ValidationExceptionCode.DEFAULT.getCode(), ValidationExceptionCode.DEFAULT.getMessage())); + } + + final String annotationName = fieldError.getCodes()[fieldError.getCodes().length - 1]; + final ValidationExceptionCode exceptionCode = ValidationExceptionCode.findByAnnotationName(annotationName); + + final String errorMessage = fieldError.getDefaultMessage(); + + return ResponseEntity.badRequest() + .body(new ExceptionResponse(exceptionCode.getCode(), errorMessage)); + } + @ExceptionHandler(UnauthorizedException.class) public ResponseEntity handleException(final UnauthorizedException e) { System.out.printf("%s : %s\n", e.getClass(), e.getMessage()); diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/exeption/dto/ValidationExceptionCode.java b/src/main/java/kr/ac/ks/cs_web_back/global/exeption/dto/ValidationExceptionCode.java new file mode 100644 index 0000000..58c1320 --- /dev/null +++ b/src/main/java/kr/ac/ks/cs_web_back/global/exeption/dto/ValidationExceptionCode.java @@ -0,0 +1,33 @@ +package kr.ac.ks.cs_web_back.global.exeption.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@Getter +@RequiredArgsConstructor +public enum ValidationExceptionCode implements ExceptionCode { + + NOT_BLANK("NotBlank", 9010, "공백일 수 없습니다."), + NOT_NULL("NotNull", 9011, "Null일 수 없습니다."), + NOT_EMPTY("NotEmpty", 9012, "비어있을 수 없습니다."), + EMAIL("Email", 9020, "유효한 이메일 형식이 아닙니다."), + SIZE("Size", 9030, "크기가 지정된 범위를 벗어났습니다."), + MAX("Max", 9031, "지정된 최댓값보다 큽니다."), + MIN("Min", 9032, "지정된 최솟값보다 작습니다."), + + // 기본값 + DEFAULT("Default", 9000, "유효성 검사에 실패했습니다."); + + private final String annotationName; + private final int code; + private final String message; + + public static ValidationExceptionCode findByAnnotationName(String name) { + return Arrays.stream(values()) + .filter(code -> code.annotationName.equals(name)) + .findFirst() + .orElse(DEFAULT); + } +} From 332a2dbb4816e9f775de6a88cf1d4f721569b6f4 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 19:15:01 +0900 Subject: [PATCH 30/43] =?UTF-8?q?feat(#5):=20=EC=97=90=EB=9F=AC=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/dto/request/MemberCreateRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java index 2b1ad84..f7e35fa 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java +++ b/src/main/java/kr/ac/ks/cs_web_back/domain/member/dto/request/MemberCreateRequest.java @@ -7,7 +7,7 @@ public record MemberCreateRequest( @Schema(description = "이메일", example = "example@ks.ac.kr") @NotBlank(message = "이메일이 입력되지 않았습니다.") - @Email(message = "이메일 형식으로 입력해 주세요.") + @Email(message = "이메일 형식이 올바르지 않습니다.") String email, @Schema(description = "비밀번호", example = "examplePassword123") From 381b81ffe0734af1ab7cdcbc2ca27299923276c7 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 19:23:59 +0900 Subject: [PATCH 31/43] =?UTF-8?q?test(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberControllerTest.java | 125 ++++++++++++++++-- .../domain/member/fixture/MemberFixture.java | 15 +-- 2 files changed, 120 insertions(+), 20 deletions(-) diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java index 75d0193..6220cfe 100644 --- a/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/member/controller/MemberControllerTest.java @@ -7,19 +7,19 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -import java.time.LocalDate; - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(controllers = MemberController.class) +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; + +@WebMvcTest(controllers = MemberController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) public class MemberControllerTest { - @MockBean + @MockitoBean private MemberService memberService; @Autowired @@ -29,22 +29,123 @@ public class MemberControllerTest { private ObjectMapper objectMapper; @Test - @DisplayName("회원가입에 성공하면 200 Ok를 반환한다.") - public void RegisteredSuccessfullyReturns200Ok() throws Exception { + @DisplayName("회원가입 성공: 회원가입에 성공하면 201 Created를 반환한다.") + public void registeredSuccessfullyReturns201Created() throws Exception { // given MemberCreateRequest request = new MemberCreateRequest( "validEmail123@ks.ac.kr", - "validPassword1234", - "exampleUsername", - LocalDate.parse("2002-09-05"), - "010-1234-5678" + "validPassword1234!", + "exampleUser" ); // when & then mockMvc.perform(post("/member/register") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isOk()); + .andExpect(status().isCreated()); + } + + @Test + @DisplayName("회원가입 실패: 이메일 필드가 없으면 400 BadRequest를 반환한다.") + public void registeredWithoutEmailReturns400BadRequest() throws Exception { + // given + MemberCreateRequest request = new MemberCreateRequest( + "", + "validPassword1234!", + "exampleUser" + ); + + // when & then + mockMvc.perform(post("/member/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("회원가입 실패: 비밀번호 필드가 없으면 400 BadRequest를 반환한다.") + public void registeredWithoutPasswordReturns400BadRequest() throws Exception { + // given + MemberCreateRequest request = new MemberCreateRequest( + "example@ks.ac.kr", + "", + "exampleUser" + ); + + // when & then + mockMvc.perform(post("/member/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); } + @Test + @DisplayName("회원가입 실패: 유저네임 필드가 없으면 400 BadRequest를 반환한다.") + public void registeredWithoutUsernameReturns400BadRequest() throws Exception { + // given + MemberCreateRequest request = new MemberCreateRequest( + "example@ks.ac.kr", + "validPassword1234!", + "" + ); + + // when & then + mockMvc.perform(post("/member/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("회원가입 실패: 입력된 이메일 형식이 유효하지 않으면 400 BadRequest를 반환한다.") + public void registeredWithInvalidEmailReturns400BadRequest() throws Exception { + // given + MemberCreateRequest request = new MemberCreateRequest( + "invalidEmail", + "validPassword1234!", + "exampleUser" + ); + + // when & then + mockMvc.perform(post("/member/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("회원가입 실패: 비밀번호가 24글자를 초과하면 400 BadRequest를 반환한다.") + public void tooLongPasswordReturns400BadRequest() throws Exception { + // given + MemberCreateRequest request = new MemberCreateRequest( + "invalidEmail", + "password".repeat(10), + "exampleUser" + ); + + // when & then + mockMvc.perform(post("/member/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("회원가입 실패: 유저네임이 12글자를 초과하면 400 BadRequest를 반환한다.") + public void tooLongUsernameReturns400BadRequest() throws Exception { + // given + MemberCreateRequest request = new MemberCreateRequest( + "invalidEmail", + "validPassword1234!", + "username".repeat(10) + ); + + // when & then + mockMvc.perform(post("/member/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + } diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java b/src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java index 13deb3e..9e88e90 100644 --- a/src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/member/fixture/MemberFixture.java @@ -8,14 +8,13 @@ public class MemberFixture { public static Member memberFixture() { - MemberCreateRequest request = new MemberCreateRequest( - "example@ks.ac.kr", - "password123", - "username", - LocalDate.parse("2002-09-05"), - "010-1234-5678" - ); - return Member.of(request); + return Member.builder() + .email("example@ks.ac.kr") + .password("examplePassword1234!") + .username("exampleUser") + .birthdate(LocalDate.parse("2002-09-05")) + .number("055-663-5140") + .build(); } } From ebd257afdf45da2bd641bcd116e942d588e181f8 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 20:09:58 +0900 Subject: [PATCH 32/43] =?UTF-8?q?chore(#5):=20h2-console=20=EB=B3=B4?= =?UTF-8?q?=EC=95=88=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/ac/ks/cs_web_back/global/config/SecurityConfig.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java b/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java index 347e02b..8dc49ea 100644 --- a/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java +++ b/src/main/java/kr/ac/ks/cs_web_back/global/config/SecurityConfig.java @@ -8,6 +8,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.boot.autoconfigure.security.servlet.PathRequest; @Configuration @EnableWebSecurity @@ -18,12 +19,16 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) + // H2 콘솔을 위한 설정 + .headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin())) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authz -> authz + .requestMatchers(PathRequest.toH2Console()).permitAll() // H2 콘솔 경로 허용 .requestMatchers("/member/register", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ); From 759fe0889b6a0a70ad475a6bf07b980165385c08 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 20:22:03 +0900 Subject: [PATCH 33/43] =?UTF-8?q?test(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberServiceTest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java new file mode 100644 index 0000000..1a74785 --- /dev/null +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java @@ -0,0 +1,68 @@ +package kr.ac.ks.cs_web_back.domain.member.service; + +import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; +import kr.ac.ks.cs_web_back.domain.member.model.Member; +import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +public class MemberServiceTest { + + @Mock + private MemberRepository memberRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @InjectMocks + private MemberService memberService; + + @Test + @DisplayName("회원가입 성공: 멤버 객체를 생성 후 생성된 멤버의 id를 반환한다.") + void shouldCreateMemberAndReturnsCreatedId() { + // given + MemberCreateRequest request = new MemberCreateRequest( + "example@ks.ac.kr", + "validPassword1234!", + "exampleUser" + ); + + given(passwordEncoder.encode(anyString())).willReturn("encodedPassword"); + given(memberRepository.existsByEmail(anyString())).willReturn(false); + given(memberRepository.existsByUsername(anyString())).willReturn(false); + + given(memberRepository.save(any(Member.class))).willAnswer(invocation -> { + Member memberToSave = invocation.getArgument(0); + ReflectionTestUtils.setField(memberToSave, "id", 1L); + return memberToSave; + }); + + // when + Long createdId = memberService.createMember(request); + + // then + ArgumentCaptor memberCaptor = ArgumentCaptor.forClass(Member.class); + verify(memberRepository).save(memberCaptor.capture()); + Member savedMember = memberCaptor.getValue(); + + assertThat(createdId).isEqualTo(1L); + + assertThat(savedMember.getEmail()).isEqualTo(request.email()); + assertThat(savedMember.getUsername()).isEqualTo(request.username()); + assertThat(savedMember.getPassword()).isEqualTo("encodedPassword"); + } +} From 599083819988ae4f0fdd3805a4db5a219cbbc063 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 20:33:18 +0900 Subject: [PATCH 34/43] =?UTF-8?q?chore(#5):=20jacoco=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/build.gradle b/build.gradle index 943d369..f2b1e64 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.4.5' id 'io.spring.dependency-management' version '1.1.7' + id 'jacoco' } group = 'kr.ac.ks' @@ -13,6 +14,10 @@ java { } } +jacoco { + toolVersion = "0.8.9" +} + configurations { compileOnly { extendsFrom annotationProcessor @@ -41,3 +46,32 @@ dependencies { tasks.named('test') { useJUnitPlatform() } + +test { + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test + + reports { + xml.required.set(true) + html.required.set(true) + + // QueryDSL Q클래스 제외 + def Qdomains = [] + for (qPattern in "**/QA".."**/QZ") { + Qdomains.add(qPattern + "*") + } + + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, + exclude: [] + Qdomains) + })) + } + + xml.destination file("${buildDir}/jacoco/index.xml") + html.destination file("${buildDir}/jacoco/index.html") + } +} \ No newline at end of file From 55d961f9d7dad88564d6d4b9c7c67cf935312a04 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 21:04:29 +0900 Subject: [PATCH 35/43] =?UTF-8?q?chore(#5):=20jacoco=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index f2b1e64..1259353 100644 --- a/build.gradle +++ b/build.gradle @@ -57,21 +57,24 @@ jacocoTestReport { reports { xml.required.set(true) html.required.set(true) + } - // QueryDSL Q클래스 제외 - def Qdomains = [] - for (qPattern in "**/QA".."**/QZ") { - Qdomains.add(qPattern + "*") - } - - afterEvaluate { - classDirectories.setFrom(files(classDirectories.files.collect { - fileTree(dir: it, - exclude: [] + Qdomains) - })) - } + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, excludes: [ + "**/*Application.class", + "**/config/**", + "**/common/**", + "**/exeption/**", + "**/response/**", + "**/swagger/**", + "**/code/**", - xml.destination file("${buildDir}/jacoco/index.xml") - html.destination file("${buildDir}/jacoco/index.html") + "**/dto/**" + ]) + })) } + + xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml") + html.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.html") } \ No newline at end of file From 4d31f5bdcb5fb72be8c1a5835b51e9bd71d17624 Mon Sep 17 00:00:00 2001 From: wooki Date: Sat, 2 Aug 2025 21:04:40 +0900 Subject: [PATCH 36/43] =?UTF-8?q?chore(#5):=20PR=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=EC=8B=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BB=A4=EB=B2=84?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EC=B8=A1=EC=A0=95=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20github=20workflow=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..45ebb46 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: Java CI with Gradle and JaCoCo + +on: + pull_request: + branches: [ "main", "develop" ] + +permissions: + contents: read + pull-requests: write # PR에 댓글을 쓰기 위한 권한 + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Run build with Gradle + run: ./gradlew build + + - name: Add JaCoCo Report to PR + id: jacoco + uses: madrapps/jacoco-report@v1.6.1 + with: + paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 40 # 전체 커버리지 최소값 (옵션) + min-coverage-changed-files: 60 # 변경된 파일의 커버리지 최소값 (옵션) + title: 'Test Coverage Report' + update-comment: true From 33846c06ae2658587d82643f9c86a9a2d575bad1 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 3 Aug 2025 15:08:16 +0900 Subject: [PATCH 37/43] =?UTF-8?q?test(#5):=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberServiceTest.java | 99 +++++++++++++------ 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java b/src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java index 1a74785..226e7a2 100644 --- a/src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java +++ b/src/test/java/kr/ac/ks/cs_web_back/domain/member/service/MemberServiceTest.java @@ -1,36 +1,35 @@ package kr.ac.ks.cs_web_back.domain.member.service; +import kr.ac.ks.cs_web_back.domain.member.controller.code.MemberExceptionCode; import kr.ac.ks.cs_web_back.domain.member.dto.request.MemberCreateRequest; import kr.ac.ks.cs_web_back.domain.member.model.Member; import kr.ac.ks.cs_web_back.domain.member.repository.MemberRepository; +import kr.ac.ks.cs_web_back.global.exeption.domain.ConflictException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -@ExtendWith(MockitoExtension.class) +@SpringBootTest +@Transactional +@Sql(statements = "ALTER TABLE member ALTER COLUMN id RESTART WITH 1", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) public class MemberServiceTest { - @Mock + @Autowired private MemberRepository memberRepository; - @Mock - private PasswordEncoder passwordEncoder; - - @InjectMocks + @Autowired private MemberService memberService; + @Autowired + private PasswordEncoder passwordEncoder; + @Test @DisplayName("회원가입 성공: 멤버 객체를 생성 후 생성된 멤버의 id를 반환한다.") void shouldCreateMemberAndReturnsCreatedId() { @@ -41,28 +40,66 @@ void shouldCreateMemberAndReturnsCreatedId() { "exampleUser" ); - given(passwordEncoder.encode(anyString())).willReturn("encodedPassword"); - given(memberRepository.existsByEmail(anyString())).willReturn(false); - given(memberRepository.existsByUsername(anyString())).willReturn(false); - - given(memberRepository.save(any(Member.class))).willAnswer(invocation -> { - Member memberToSave = invocation.getArgument(0); - ReflectionTestUtils.setField(memberToSave, "id", 1L); - return memberToSave; - }); - // when Long createdId = memberService.createMember(request); // then - ArgumentCaptor memberCaptor = ArgumentCaptor.forClass(Member.class); - verify(memberRepository).save(memberCaptor.capture()); - Member savedMember = memberCaptor.getValue(); + Member savedMember = memberRepository.findById(createdId).orElseThrow(); assertThat(createdId).isEqualTo(1L); - assertThat(savedMember.getEmail()).isEqualTo(request.email()); assertThat(savedMember.getUsername()).isEqualTo(request.username()); - assertThat(savedMember.getPassword()).isEqualTo("encodedPassword"); + assertThat(passwordEncoder.matches(request.password(), savedMember.getPassword())).isTrue(); + } + + + @Test + @DisplayName("회원가입 실패: 중복된 이메일로 가입 시 ConflictException이 발생한다.") + void registeredFailedByConflictEmail() { + // given + MemberCreateRequest request = new MemberCreateRequest( + "example@ks.ac.kr", + "validpassword1234!", + "exampleUser" + ); + + memberRepository.saveAndFlush(Member.builder() + .email(request.email()) + .password("anyPassword") + .username("anyUsername") + .build()); + + // when & then + assertThatThrownBy(() -> memberService.createMember(request)) + .isInstanceOf(ConflictException.class) + .satisfies(exception -> { + ConflictException e = (ConflictException) exception; + assertThat(e.getExceptionCode()).isEqualTo(MemberExceptionCode.CONFLICT_EMAIL); + }); + } + + @Test + @DisplayName("회원가입 실패: 중복된 유저명으로 가입 시 ConflictException이 발생한다.") + void registeredFailedByConflictUsername() { + // given + MemberCreateRequest request = new MemberCreateRequest( + "example@ks.ac.kr", + "validpassword1234!", + "exampleUser" + ); + + memberRepository.saveAndFlush(Member.builder() + .email("anyEmail@naver.com") + .password("anyPassword") + .username(request.username()) + .build()); + + // when & then + assertThatThrownBy(() -> memberService.createMember(request)) + .isInstanceOf(ConflictException.class) + .satisfies(exception -> { + ConflictException e = (ConflictException) exception; + assertThat(e.getExceptionCode()).isEqualTo(MemberExceptionCode.CONFLICT_USERNAME); + }); } } From 2b2c914c64b74a06b0d899d0339eca02ea751466 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 3 Aug 2025 15:08:30 +0900 Subject: [PATCH 38/43] =?UTF-8?q?chore(#5):=20jacoco=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=20=EC=84=B8=ED=8C=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 1259353..0b75a7b 100644 --- a/build.gradle +++ b/build.gradle @@ -52,29 +52,29 @@ test { } jacocoTestReport { - dependsOn test + dependsOn(test) - reports { - xml.required.set(true) - html.required.set(true) - } - - afterEvaluate { - classDirectories.setFrom(files(classDirectories.files.collect { - fileTree(dir: it, excludes: [ - "**/*Application.class", - "**/config/**", - "**/common/**", - "**/exeption/**", - "**/response/**", - "**/swagger/**", - "**/code/**", + reports { + xml.required.set(true) + html.required.set(true) - "**/dto/**" - ]) - })) - } + // 생성될 리포트의 위치 지정 + xml.destination(file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")) + html.destination(file("${buildDir}/reports/jacoco/test/jacocoTestReport.html")) + } - xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml") - html.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.html") + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, excludes: [ + "**/*Application.class", + "**/config/**", + "**/common/**", + "**/exeption/**", + "**/response/**", + "**/swagger/**", + "**/code/**", + "**/dto/**" + ]) + })) + } } \ No newline at end of file From 2de405c16de9a5002883da9fe8368d8a6adf8d7d Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 3 Aug 2025 15:21:48 +0900 Subject: [PATCH 39/43] =?UTF-8?q?fix(#5):=20CI=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 0b75a7b..5412bdc 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,7 @@ dependencies { tasks.named('test') { useJUnitPlatform() + systemProperty 'spring.profiles.active', 'dev' } test { From c07c5251f83006a520fa924267ddd6da7a3351c0 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 3 Aug 2025 15:32:23 +0900 Subject: [PATCH 40/43] =?UTF-8?q?fix(#5):=20CI=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- src/test/resources/application-test.properties | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/application-test.properties diff --git a/build.gradle b/build.gradle index 5412bdc..e533a4e 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ dependencies { tasks.named('test') { useJUnitPlatform() - systemProperty 'spring.profiles.active', 'dev' + systemProperty 'spring.profiles.active', 'test' } test { diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..f075f76 --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,6 @@ +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=update +spring.datasource.generate-unique-name=false \ No newline at end of file From a5781b315336ab67ae4828d91c8be2139ed27024 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 3 Aug 2025 15:34:21 +0900 Subject: [PATCH 41/43] =?UTF-8?q?fix(#5):=20CI=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8B=A4=ED=8C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45ebb46..dcb9594 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Run build with Gradle - run: ./gradlew build + run: ./gradlew build test - name: Add JaCoCo Report to PR id: jacoco From f48fdcdbde0471649daca3e0e491a3c7b430090a Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 3 Aug 2025 15:46:29 +0900 Subject: [PATCH 42/43] =?UTF-8?q?fix(#5):=20CI=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcb9594..ea3f6ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Run build with Gradle - run: ./gradlew build test + run: ./gradlew build test -Dspring.profiles.active=test - name: Add JaCoCo Report to PR id: jacoco From 3c2367567c780fd9efc42ea97c68d533421bb0c0 Mon Sep 17 00:00:00 2001 From: wooki Date: Sun, 3 Aug 2025 16:01:47 +0900 Subject: [PATCH 43/43] =?UTF-8?q?fix(#5):=20CI=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 +++- build.gradle | 1 - src/main/resources/application.properties | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea3f6ea..7001101 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,9 @@ jobs: uses: gradle/actions/setup-gradle@v3 - name: Run build with Gradle - run: ./gradlew build test -Dspring.profiles.active=test + env: + SPRING_PROFILES_ACTIVE: test + run: ./gradlew build test - name: Add JaCoCo Report to PR id: jacoco diff --git a/build.gradle b/build.gradle index e533a4e..0b75a7b 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,6 @@ dependencies { tasks.named('test') { useJUnitPlatform() - systemProperty 'spring.profiles.active', 'test' } test { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bcc6e41..79f5388 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,4 @@ -spring.config.import=env.properties +spring.config.import=optional:env.properties spring.application.name=cs_web_back spring.profiles.active=${PROPERTY_PROFILE} \ No newline at end of file