From 5be9a03e70d7d3993751921eeef866d5c8e90e9b Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 27 Jul 2025 17:00:13 +0900 Subject: [PATCH 01/16] =?UTF-8?q?refactor=20:=20=EC=9C=A0=EC=A0=80/?= =?UTF-8?q?=EB=82=B4=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/chooz/user/domain/User.java | 12 +++++++----- .../user/presentation/dto/UserInfoResponse.java | 12 ++++++++++-- .../user/presentation/dto/UserMyInfoResponse.java | 11 +++++++++-- .../com/chooz/support/fixture/UserFixture.java | 5 +++-- src/test/java/com/chooz/user/domain/UserTest.java | 2 +- .../user/presentation/UserControllerTest.java | 14 ++++++++------ 6 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/chooz/user/domain/User.java b/src/main/java/com/chooz/user/domain/User.java index 35e2f2c4..011983b4 100644 --- a/src/main/java/com/chooz/user/domain/User.java +++ b/src/main/java/com/chooz/user/domain/User.java @@ -27,19 +27,21 @@ public class User extends BaseEntity { private String profileUrl; - @Enumerated(jakarta.persistence.EnumType.STRING) - public Role role; + private boolean is_onboard; + + private boolean notification; @Builder - private User(Long id, String nickname, String profileUrl, Role role) { + private User(Long id, String nickname, String profileUrl, boolean is_onboard, boolean notification) { this.id = id; this.nickname = nickname; this.profileUrl = profileUrl; - this.role = role; + this.is_onboard = is_onboard; + this.notification = notification; } public static User create(String nickname, String profileUrl) { - return new User(null, nickname, profileUrl, Role.USER); + return new User(null, nickname, profileUrl, true, false); } } diff --git a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java index f7cefc44..9c880ec5 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java @@ -5,9 +5,17 @@ public record UserInfoResponse( Long id, String nickname, - String profileUrl + String profileUrl, + boolean is_onboard, + boolean notification ) { public static UserInfoResponse of(User user) { - return new UserInfoResponse(user.getId(), user.getNickname(), user.getProfileUrl()); + return new UserInfoResponse( + user.getId(), + user.getNickname(), + user.getProfileUrl(), + user.is_onboard(), + user.isNotification() + ); } } diff --git a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java index 8fb8d52e..8283e9e7 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java @@ -7,9 +7,16 @@ public record UserMyInfoResponse( Long id, String nickname, String profileImageUrl, - Role role + boolean is_onboard, + boolean notification ) { public static UserMyInfoResponse of(User user) { - return new UserMyInfoResponse(user.getId(), user.getNickname(), user.getProfileUrl(), user.getRole()); + return new UserMyInfoResponse( + user.getId(), + user.getNickname(), + user.getProfileUrl(), + user.is_onboard(), + user.isNotification() + ); } } diff --git a/src/test/java/com/chooz/support/fixture/UserFixture.java b/src/test/java/com/chooz/support/fixture/UserFixture.java index a3b47c55..5f1e889e 100644 --- a/src/test/java/com/chooz/support/fixture/UserFixture.java +++ b/src/test/java/com/chooz/support/fixture/UserFixture.java @@ -11,8 +11,9 @@ public static User createDefaultUser() { public static User.UserBuilder createUserBuilder() { return User.builder() - .role(Role.USER) .nickname("nickname") - .profileUrl("http://example.com/profile.png"); + .profileUrl("http://example.com/profile.png") + .is_onboard(true) + .notification(false); } } diff --git a/src/test/java/com/chooz/user/domain/UserTest.java b/src/test/java/com/chooz/user/domain/UserTest.java index 145e7ceb..c132f8cf 100644 --- a/src/test/java/com/chooz/user/domain/UserTest.java +++ b/src/test/java/com/chooz/user/domain/UserTest.java @@ -9,7 +9,7 @@ class UserTest { @Test @DisplayName("user Entity 생성") - void create() throws Exception { + void create() { //given String nickname = "nickname"; diff --git a/src/test/java/com/chooz/user/presentation/UserControllerTest.java b/src/test/java/com/chooz/user/presentation/UserControllerTest.java index 7e26eb11..51178f8f 100644 --- a/src/test/java/com/chooz/user/presentation/UserControllerTest.java +++ b/src/test/java/com/chooz/user/presentation/UserControllerTest.java @@ -12,8 +12,7 @@ import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; -import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.JsonFieldType.*; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; @@ -28,7 +27,7 @@ class UserControllerTest extends RestDocsTest { @DisplayName("유저 정보 조회") void findUserInfo() throws Exception { //given - UserInfoResponse response = new UserInfoResponse(1L, "nickname", "https://image.com/profile-image"); + UserInfoResponse response = new UserInfoResponse(1L, "nickname", "https://image.com/profile-image", true, false); given(userService.findById(1L)) .willReturn(response); @@ -43,7 +42,9 @@ void findUserInfo() throws Exception { responseFields( fieldWithPath("id").description("유저 아이디").type(NUMBER), fieldWithPath("nickname").description("닉네임").type(STRING), - fieldWithPath("profileUrl").description("프로필 이미지 URL").type(STRING) + fieldWithPath("profileUrl").description("프로필 이미지 URL").type(STRING), + fieldWithPath("is_onboard").description("온보딩 유저 여부").type(BOOLEAN), + fieldWithPath("notification").description("알림 설정 여부").type(BOOLEAN) ) )); } @@ -53,7 +54,7 @@ void findUserInfo() throws Exception { @DisplayName("본인 정보 조회") void findMe() throws Exception { //given - UserMyInfoResponse response = new UserMyInfoResponse(1L, "nickname", "https://image.com/profile-image", Role.USER); + UserMyInfoResponse response = new UserMyInfoResponse(1L, "nickname", "https://image.com/profile-image", true, false); given(userService.findByMe(1L)) .willReturn(response); @@ -68,7 +69,8 @@ void findMe() throws Exception { fieldWithPath("id").description("유저 아이디").type(NUMBER), fieldWithPath("nickname").description("닉네임").type(STRING), fieldWithPath("profileImageUrl").description("프로필 이미지 URL").type(STRING), - fieldWithPath("role").description("유저 권한").type(STRING) + fieldWithPath("is_onboard").description("온보딩 유저 여부").type(BOOLEAN), + fieldWithPath("notification").description("알림 설정 여부").type(BOOLEAN) ) )); } From f6d4da10c00c642658ed2512c224eb949e7add1b Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 27 Jul 2025 20:35:29 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/chooz/common/exception/ErrorCode.java | 1 + .../chooz/user/application/UserService.java | 19 +++++++-- .../com/chooz/user/domain/OnboardingStep.java | 7 ++++ src/main/java/com/chooz/user/domain/User.java | 33 +++++++++------ .../chooz/user/domain/UserOnboardingStep.java | 40 +++++++++++++++++++ .../domain/UserOnboardingStepRepository.java | 12 ++++++ .../com/chooz/user/domain/UserRepository.java | 11 +++++ .../user/presentation/UserController.java | 14 ++++--- .../presentation/dto/OnboardingRequest.java | 17 ++++++++ .../presentation/dto/UserMyInfoResponse.java | 8 +++- 10 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/chooz/user/domain/OnboardingStep.java create mode 100644 src/main/java/com/chooz/user/domain/UserOnboardingStep.java create mode 100644 src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java create mode 100644 src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java diff --git a/src/main/java/com/chooz/common/exception/ErrorCode.java b/src/main/java/com/chooz/common/exception/ErrorCode.java index ec823426..e7e7ed24 100644 --- a/src/main/java/com/chooz/common/exception/ErrorCode.java +++ b/src/main/java/com/chooz/common/exception/ErrorCode.java @@ -45,6 +45,7 @@ public enum ErrorCode { SINGLE_POLL_ALLOWS_MAXIMUM_ONE_CHOICE("단일 투표인 경우 최대 하나의 선택지만 투표 가능"), DUPLICATE_POLL_CHOICE("복수 투표의 경우 중복된 선택지가 있으면 안 됨"), NOT_POST_POLL_CHOICE_ID("게시글의 투표 선택지가 아님"), + INVALID_ONBOARDING_STEP("잘못된 온보딩 단계"), //401 EXPIRED_TOKEN("토큰이 만료됐습니다."), diff --git a/src/main/java/com/chooz/user/application/UserService.java b/src/main/java/com/chooz/user/application/UserService.java index 1181a6de..73710821 100644 --- a/src/main/java/com/chooz/user/application/UserService.java +++ b/src/main/java/com/chooz/user/application/UserService.java @@ -2,14 +2,15 @@ import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; -import com.chooz.user.domain.User; -import com.chooz.user.domain.UserRepository; +import com.chooz.user.domain.*; import com.chooz.user.presentation.dto.UserInfoResponse; import com.chooz.user.presentation.dto.UserMyInfoResponse; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.Optional; @Service @@ -19,10 +20,11 @@ public class UserService { private final UserRepository userRepository; private final NicknameGenerator nicknameGenerator; + private final UserOnboardingStepRepository userOnboardingStepRepository; @Transactional public Long createUser(String nickname, String profileImageUrl) { - User user = userRepository.save(User.create(getNickname(nickname), getProfileImage(profileImageUrl))); + User user = userRepository.save(User.create(getNickname(nickname), getProfileImage(profileImageUrl), List.of())); return user.getId(); } @@ -47,4 +49,15 @@ public UserMyInfoResponse findByMe(Long userId) { .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); return UserMyInfoResponse.of(user); } + + @Transactional + public UserMyInfoResponse completeStep(Long userId, OnboardingStep step) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); + if(!userOnboardingStepRepository.existsByUserAndStep(user, step)) { + userOnboardingStepRepository.save(UserOnboardingStep.create(user, step)); + } + return UserMyInfoResponse.of(userRepository.findByIdFetchOnboardingSteps(user.getId()) + .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND))); + } } diff --git a/src/main/java/com/chooz/user/domain/OnboardingStep.java b/src/main/java/com/chooz/user/domain/OnboardingStep.java new file mode 100644 index 00000000..db252ff5 --- /dev/null +++ b/src/main/java/com/chooz/user/domain/OnboardingStep.java @@ -0,0 +1,7 @@ +package com.chooz.user.domain; + + +public enum OnboardingStep { + WELCOME_GUIDE, + FIRST_VOTE +} diff --git a/src/main/java/com/chooz/user/domain/User.java b/src/main/java/com/chooz/user/domain/User.java index 011983b4..cfa54fe1 100644 --- a/src/main/java/com/chooz/user/domain/User.java +++ b/src/main/java/com/chooz/user/domain/User.java @@ -1,16 +1,16 @@ package com.chooz.user.domain; import com.chooz.common.domain.BaseEntity; -import jakarta.persistence.Entity; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.ArrayList; +import java.util.List; + +import static com.chooz.common.util.Validator.validateNull; + @Getter @Entity @Table(name = "users") @@ -27,21 +27,30 @@ public class User extends BaseEntity { private String profileUrl; - private boolean is_onboard; - private boolean notification; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List onboardingSteps = new ArrayList<>(); + @Builder - private User(Long id, String nickname, String profileUrl, boolean is_onboard, boolean notification) { + private User( + Long id, + String nickname, + String profileUrl, + List onboardingSteps, + boolean notification + ) { + validateNull(nickname, nickname, onboardingSteps, notification); this.id = id; this.nickname = nickname; this.profileUrl = profileUrl; - this.is_onboard = is_onboard; + this.onboardingSteps = onboardingSteps; + onboardingSteps.forEach(step -> step.setUser(this)); this.notification = notification; } - public static User create(String nickname, String profileUrl) { - return new User(null, nickname, profileUrl, true, false); + public static User create(String nickname, String profileUrl, ListonboardingSteps) { + return new User(null, nickname, profileUrl, onboardingSteps, false); } } diff --git a/src/main/java/com/chooz/user/domain/UserOnboardingStep.java b/src/main/java/com/chooz/user/domain/UserOnboardingStep.java new file mode 100644 index 00000000..abcef0be --- /dev/null +++ b/src/main/java/com/chooz/user/domain/UserOnboardingStep.java @@ -0,0 +1,40 @@ +package com.chooz.user.domain; + +import com.chooz.common.domain.BaseEntity; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import static com.chooz.common.util.Validator.validateNull; + +@Getter +@Entity +@Table(name = "user_onboarding_step") +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class UserOnboardingStep extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private User user; + + @Enumerated(EnumType.STRING) + private OnboardingStep step; + + @Builder + private UserOnboardingStep(Long id, User user, OnboardingStep step) { + this.id = id; + this.user = user; + this.step = step; + } + public static UserOnboardingStep create(User user, OnboardingStep step){ + return new UserOnboardingStep(null, user, step); + } + public void setUser(User user) { + validateNull(user); + this.user = user; + } +} diff --git a/src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java b/src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java new file mode 100644 index 00000000..0cfeb410 --- /dev/null +++ b/src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java @@ -0,0 +1,12 @@ +package com.chooz.user.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserOnboardingStepRepository extends JpaRepository { + boolean existsByUserAndStep(User user, OnboardingStep step); +} diff --git a/src/main/java/com/chooz/user/domain/UserRepository.java b/src/main/java/com/chooz/user/domain/UserRepository.java index af296786..80f5ad4a 100644 --- a/src/main/java/com/chooz/user/domain/UserRepository.java +++ b/src/main/java/com/chooz/user/domain/UserRepository.java @@ -1,8 +1,19 @@ package com.chooz.user.domain; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { + @Query(""" + SELECT u + FROM User u + JOIN FETCH u.onboardingSteps + where u.id = :userId + """) + Optional findByIdFetchOnboardingSteps(@Param("userId") Long userId); } diff --git a/src/main/java/com/chooz/user/presentation/UserController.java b/src/main/java/com/chooz/user/presentation/UserController.java index df2731cd..bff54374 100644 --- a/src/main/java/com/chooz/user/presentation/UserController.java +++ b/src/main/java/com/chooz/user/presentation/UserController.java @@ -2,16 +2,13 @@ import com.chooz.auth.domain.UserInfo; import com.chooz.user.application.UserService; +import com.chooz.user.presentation.dto.OnboardingRequest; import com.chooz.user.presentation.dto.UserInfoResponse; import com.chooz.user.presentation.dto.UserMyInfoResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -32,4 +29,11 @@ public ResponseEntity findMyInfo( return ResponseEntity.ok(userService.findByMe(userInfo.userId())); } + @PostMapping("/onboarding") + public ResponseEntity findUserInfo( + @RequestBody OnboardingRequest request, + @AuthenticationPrincipal UserInfo userInfo + ) { + return ResponseEntity.ok(userService.completeStep(userInfo.userId(), request.step())); + } } diff --git a/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java b/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java new file mode 100644 index 00000000..9cbb2171 --- /dev/null +++ b/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java @@ -0,0 +1,17 @@ +package com.chooz.user.presentation.dto; + +import com.chooz.common.exception.BadRequestException; +import com.chooz.common.exception.ErrorCode; +import com.chooz.user.domain.OnboardingStep; +import jakarta.validation.constraints.NotNull; + +public record OnboardingRequest( + @NotNull + OnboardingStep step +) { + public OnboardingRequest { + if (step != OnboardingStep.WELCOME_GUIDE && step != OnboardingStep.FIRST_VOTE) { + throw new BadRequestException(ErrorCode.INVALID_ONBOARDING_STEP); + } + } +} diff --git a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java index 8283e9e7..00798b44 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java @@ -1,13 +1,17 @@ package com.chooz.user.presentation.dto; +import com.chooz.user.domain.OnboardingStep; import com.chooz.user.domain.Role; import com.chooz.user.domain.User; +import com.chooz.user.domain.UserOnboardingStep; + +import java.util.List; public record UserMyInfoResponse( Long id, String nickname, String profileImageUrl, - boolean is_onboard, + List onboardingSteps, boolean notification ) { public static UserMyInfoResponse of(User user) { @@ -15,7 +19,7 @@ public static UserMyInfoResponse of(User user) { user.getId(), user.getNickname(), user.getProfileUrl(), - user.is_onboard(), + user.getOnboardingSteps().stream().map(UserOnboardingStep::getStep).toList(), user.isNotification() ); } From 41ac07f68b0bc7467dea51e110b84601c272e729 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 27 Jul 2025 20:40:34 +0900 Subject: [PATCH 03/16] =?UTF-8?q?refactor=20:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=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 --- .../java/com/chooz/user/application/UserService.java | 12 ++++++++++++ .../com/chooz/user/presentation/UserController.java | 4 ++-- .../user/presentation/dto/UserInfoResponse.java | 10 +++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/chooz/user/application/UserService.java b/src/main/java/com/chooz/user/application/UserService.java index 73710821..cb840aa7 100644 --- a/src/main/java/com/chooz/user/application/UserService.java +++ b/src/main/java/com/chooz/user/application/UserService.java @@ -50,6 +50,18 @@ public UserMyInfoResponse findByMe(Long userId) { return UserMyInfoResponse.of(user); } + public UserInfoResponse findByIdFetchOnboardingSteps(Long userId) { + User user = userRepository.findByIdFetchOnboardingSteps(userId) + .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); + return UserInfoResponse.of(user); + } + + public UserMyInfoResponse findByMeFetchOnboardingSteps(Long userId) { + User user = userRepository.findByIdFetchOnboardingSteps(userId) + .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); + return UserMyInfoResponse.of(user); + } + @Transactional public UserMyInfoResponse completeStep(Long userId, OnboardingStep step) { User user = userRepository.findById(userId) diff --git a/src/main/java/com/chooz/user/presentation/UserController.java b/src/main/java/com/chooz/user/presentation/UserController.java index bff54374..5a6cbbae 100644 --- a/src/main/java/com/chooz/user/presentation/UserController.java +++ b/src/main/java/com/chooz/user/presentation/UserController.java @@ -19,14 +19,14 @@ public class UserController { @GetMapping("/{userId}") public ResponseEntity findUserInfo(@PathVariable("userId") Long userId) { - return ResponseEntity.ok(userService.findById(userId)); + return ResponseEntity.ok(userService.findByIdFetchOnboardingSteps(userId)); } @GetMapping("/me") public ResponseEntity findMyInfo( @AuthenticationPrincipal UserInfo userInfo ) { - return ResponseEntity.ok(userService.findByMe(userInfo.userId())); + return ResponseEntity.ok(userService.findByMeFetchOnboardingSteps(userInfo.userId())); } @PostMapping("/onboarding") diff --git a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java index 9c880ec5..accdec49 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java @@ -1,12 +1,16 @@ package com.chooz.user.presentation.dto; +import com.chooz.user.domain.OnboardingStep; import com.chooz.user.domain.User; +import com.chooz.user.domain.UserOnboardingStep; + +import java.util.List; public record UserInfoResponse( Long id, String nickname, - String profileUrl, - boolean is_onboard, + String profileImageUrl, + List onboardingSteps, boolean notification ) { public static UserInfoResponse of(User user) { @@ -14,7 +18,7 @@ public static UserInfoResponse of(User user) { user.getId(), user.getNickname(), user.getProfileUrl(), - user.is_onboard(), + user.getOnboardingSteps().stream().map(UserOnboardingStep::getStep).toList(), user.isNotification() ); } From ef157f9dc58018bab0b477a0e919a49ddafa2b39 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Mon, 28 Jul 2025 01:52:00 +0900 Subject: [PATCH 04/16] =?UTF-8?q?refactor=20:=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EC=83=9D=EC=84=B1=20=EB=B0=A9=EC=8B=9D=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 --- .../chooz/user/application/NicknameGenerator.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/chooz/user/application/NicknameGenerator.java b/src/main/java/com/chooz/user/application/NicknameGenerator.java index db353278..c6531559 100644 --- a/src/main/java/com/chooz/user/application/NicknameGenerator.java +++ b/src/main/java/com/chooz/user/application/NicknameGenerator.java @@ -1,6 +1,7 @@ package com.chooz.user.application; import com.chooz.user.domain.NicknameAdjectiveRepository; +import com.chooz.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -9,10 +10,20 @@ public class NicknameGenerator { private final NicknameAdjectiveRepository nicknameAdjectiveRepository; + private final UserRepository userRepository; public String generate() { - return nicknameAdjectiveRepository.findRandomNicknameAdjective() + String nickName = nicknameAdjectiveRepository.findRandomNicknameAdjective() .map(adjective -> adjective.getAdjective() + " 츄") .orElse("숨겨진 츄"); + return checkDuplicateNickname(nickName); + } + private String checkDuplicateNickname(String nickName) { + int suffix = 1; + while (userRepository.existsByNickname(nickName)){ + nickName = nickName + suffix; + suffix++; + } + return nickName; } } From b79a502dfd62795c2627f89edb4440e5805d4c77 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Tue, 5 Aug 2025 21:22:10 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat=20:=20user=20onboarding=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/chooz/common/exception/ErrorCode.java | 1 + .../chooz/user/application/UserService.java | 46 ++++++++----------- .../com/chooz/user/domain/OnboardingStep.java | 42 +++++++++++++++-- .../user/domain/OnboardingStepRepository.java | 7 +++ .../chooz/user/domain/OnboardingStepType.java | 27 +++++++++++ src/main/java/com/chooz/user/domain/User.java | 28 +++++++---- .../chooz/user/domain/UserOnboardingStep.java | 40 ---------------- .../domain/UserOnboardingStepRepository.java | 12 ----- .../com/chooz/user/domain/UserRepository.java | 8 +--- .../user/presentation/UserController.java | 10 ++-- .../presentation/dto/OnboardingRequest.java | 13 ++++-- .../presentation/dto/UserInfoResponse.java | 22 ++++++--- .../presentation/dto/UserMyInfoResponse.java | 23 +++++++--- .../fixture/OnboardingStepFixture.java | 24 ++++++++++ 14 files changed, 182 insertions(+), 121 deletions(-) create mode 100644 src/main/java/com/chooz/user/domain/OnboardingStepRepository.java create mode 100644 src/main/java/com/chooz/user/domain/OnboardingStepType.java delete mode 100644 src/main/java/com/chooz/user/domain/UserOnboardingStep.java delete mode 100644 src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java create mode 100644 src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java diff --git a/src/main/java/com/chooz/common/exception/ErrorCode.java b/src/main/java/com/chooz/common/exception/ErrorCode.java index e7e7ed24..dcb120aa 100644 --- a/src/main/java/com/chooz/common/exception/ErrorCode.java +++ b/src/main/java/com/chooz/common/exception/ErrorCode.java @@ -46,6 +46,7 @@ public enum ErrorCode { DUPLICATE_POLL_CHOICE("복수 투표의 경우 중복된 선택지가 있으면 안 됨"), NOT_POST_POLL_CHOICE_ID("게시글의 투표 선택지가 아님"), INVALID_ONBOARDING_STEP("잘못된 온보딩 단계"), + ONBOARDING_NOT_INITIALIZED("온보딩이 초기화 되지 않음."), //401 EXPIRED_TOKEN("토큰이 만료됐습니다."), diff --git a/src/main/java/com/chooz/user/application/UserService.java b/src/main/java/com/chooz/user/application/UserService.java index cb840aa7..f1940365 100644 --- a/src/main/java/com/chooz/user/application/UserService.java +++ b/src/main/java/com/chooz/user/application/UserService.java @@ -3,14 +3,15 @@ import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; import com.chooz.user.domain.*; +import com.chooz.user.presentation.dto.OnboardingRequest; import com.chooz.user.presentation.dto.UserInfoResponse; import com.chooz.user.presentation.dto.UserMyInfoResponse; import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Map; import java.util.Optional; @Service @@ -20,56 +21,49 @@ public class UserService { private final UserRepository userRepository; private final NicknameGenerator nicknameGenerator; - private final UserOnboardingStepRepository userOnboardingStepRepository; + private final OnboardingStepRepository onboardingStepRepository; @Transactional public Long createUser(String nickname, String profileImageUrl) { - User user = userRepository.save(User.create(getNickname(nickname), getProfileImage(profileImageUrl), List.of())); + User user = userRepository.save(User.create(getOrGenerateNickname(nickname), profileImageUrl)); return user.getId(); } - private String getNickname(String nickname) { + private String getOrGenerateNickname(String nickname) { return Optional.ofNullable(nickname) - .orElseGet(() -> nicknameGenerator.generate()); - } - - private String getProfileImage(String profileImageUrl) { - return Optional.ofNullable(profileImageUrl) - .orElse(User.DEFAULT_PROFILE_URL); + .orElseGet(nicknameGenerator::generate); } + @Transactional(readOnly = true) public UserInfoResponse findById(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); return UserInfoResponse.of(user); } + @Transactional(readOnly = true) public UserMyInfoResponse findByMe(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); return UserMyInfoResponse.of(user); } - public UserInfoResponse findByIdFetchOnboardingSteps(Long userId) { - User user = userRepository.findByIdFetchOnboardingSteps(userId) - .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); - return UserInfoResponse.of(user); - } - - public UserMyInfoResponse findByMeFetchOnboardingSteps(Long userId) { - User user = userRepository.findByIdFetchOnboardingSteps(userId) - .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); - return UserMyInfoResponse.of(user); - } - @Transactional - public UserMyInfoResponse completeStep(Long userId, OnboardingStep step) { + public UserInfoResponse completeStep(Long userId, OnboardingRequest onboardingRequest) { User user = userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); - if(!userOnboardingStepRepository.existsByUserAndStep(user, step)) { - userOnboardingStepRepository.save(UserOnboardingStep.create(user, step)); + if (user.getOnboardingStep() == null) { + throw new BadRequestException(ErrorCode.ONBOARDING_NOT_INITIALIZED); } - return UserMyInfoResponse.of(userRepository.findByIdFetchOnboardingSteps(user.getId()) + UpdateOnboardingStep(user, onboardingRequest); + return UserInfoResponse.of(userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND))); } + + private void UpdateOnboardingStep(User user, OnboardingRequest onboardingRequest) { + onboardingRequest.onboardingStep().entrySet().stream() + .filter(step -> Boolean.TRUE.equals(step.getValue())) + .map(Map.Entry::getKey) + .forEach(stepType -> stepType.apply(user.getOnboardingStep())); + } } diff --git a/src/main/java/com/chooz/user/domain/OnboardingStep.java b/src/main/java/com/chooz/user/domain/OnboardingStep.java index db252ff5..a6e24a88 100644 --- a/src/main/java/com/chooz/user/domain/OnboardingStep.java +++ b/src/main/java/com/chooz/user/domain/OnboardingStep.java @@ -1,7 +1,43 @@ package com.chooz.user.domain; +import com.chooz.common.domain.BaseEntity; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; -public enum OnboardingStep { - WELCOME_GUIDE, - FIRST_VOTE +import static com.chooz.common.util.Validator.validateNull; + +@Getter +@Entity +@Table(name = "onboarding_step") +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class OnboardingStep extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private boolean welcomeGuide; + + private boolean firstVote; + + @Builder + public OnboardingStep(Long id, boolean welcomeGuide, boolean firstVote) { + this.id = id; + this.welcomeGuide = welcomeGuide; + this.firstVote = firstVote; + } + + public void completeWelcomeGuide() { + this.welcomeGuide = true; + } + + public void completeFirstVote() { + this.firstVote = true; + } + + public boolean isCompletedAll() { + return welcomeGuide && firstVote; + } } diff --git a/src/main/java/com/chooz/user/domain/OnboardingStepRepository.java b/src/main/java/com/chooz/user/domain/OnboardingStepRepository.java new file mode 100644 index 00000000..57fe577d --- /dev/null +++ b/src/main/java/com/chooz/user/domain/OnboardingStepRepository.java @@ -0,0 +1,7 @@ +package com.chooz.user.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface OnboardingStepRepository extends JpaRepository {} diff --git a/src/main/java/com/chooz/user/domain/OnboardingStepType.java b/src/main/java/com/chooz/user/domain/OnboardingStepType.java new file mode 100644 index 00000000..c96357f4 --- /dev/null +++ b/src/main/java/com/chooz/user/domain/OnboardingStepType.java @@ -0,0 +1,27 @@ +package com.chooz.user.domain; + + +import java.util.function.Consumer; +import java.util.function.Predicate; + +public enum OnboardingStepType { + + WELCOME_GUIDE(OnboardingStep::completeWelcomeGuide, OnboardingStep::isWelcomeGuide), + FIRST_VOTE(OnboardingStep::completeFirstVote, OnboardingStep::isFirstVote); + + private final Consumer action; + private final Predicate checker; + + OnboardingStepType(Consumer action, Predicate checker) { + this.action = action; + this.checker = checker; + } + + public void apply(OnboardingStep step) { + this.action.accept(step); + } + + public boolean check(OnboardingStep step) { + return this.checker.test(step); + } +} diff --git a/src/main/java/com/chooz/user/domain/User.java b/src/main/java/com/chooz/user/domain/User.java index cfa54fe1..449eb604 100644 --- a/src/main/java/com/chooz/user/domain/User.java +++ b/src/main/java/com/chooz/user/domain/User.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import static com.chooz.common.util.Validator.validateNull; @@ -17,7 +18,7 @@ @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class User extends BaseEntity { - public static final String DEFAULT_PROFILE_URL = "https://image.chooz.site/default_profile.png"; + private static final String DEFAULT_PROFILE_URL = "https://cdn.chooz.site/default_profile.png"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -29,28 +30,35 @@ public class User extends BaseEntity { private boolean notification; - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List onboardingSteps = new ArrayList<>(); + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "onboarding_step_id", unique = true) + private OnboardingStep onboardingStep; @Builder private User( Long id, String nickname, String profileUrl, - List onboardingSteps, - boolean notification + boolean notification, + OnboardingStep onboardingStep ) { - validateNull(nickname, nickname, onboardingSteps, notification); this.id = id; this.nickname = nickname; this.profileUrl = profileUrl; - this.onboardingSteps = onboardingSteps; - onboardingSteps.forEach(step -> step.setUser(this)); this.notification = notification; + this.onboardingStep = onboardingStep; } - public static User create(String nickname, String profileUrl, ListonboardingSteps) { - return new User(null, nickname, profileUrl, onboardingSteps, false); + public static User create(String nickname, String profileUrl) { + return new User(null, nickname, getOrDefaultProfileImage(profileUrl), false, new OnboardingStep()); } + private static String getOrDefaultProfileImage(String profileImageUrl) { + return Optional.ofNullable(profileImageUrl) + .orElse(User.DEFAULT_PROFILE_URL); + } + + public boolean hasCompletedOnboarding() { + return onboardingStep != null && onboardingStep.isCompletedAll(); + } } diff --git a/src/main/java/com/chooz/user/domain/UserOnboardingStep.java b/src/main/java/com/chooz/user/domain/UserOnboardingStep.java deleted file mode 100644 index abcef0be..00000000 --- a/src/main/java/com/chooz/user/domain/UserOnboardingStep.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.chooz.user.domain; - -import com.chooz.common.domain.BaseEntity; -import jakarta.persistence.*; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import static com.chooz.common.util.Validator.validateNull; - -@Getter -@Entity -@Table(name = "user_onboarding_step") -@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class UserOnboardingStep extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - private User user; - - @Enumerated(EnumType.STRING) - private OnboardingStep step; - - @Builder - private UserOnboardingStep(Long id, User user, OnboardingStep step) { - this.id = id; - this.user = user; - this.step = step; - } - public static UserOnboardingStep create(User user, OnboardingStep step){ - return new UserOnboardingStep(null, user, step); - } - public void setUser(User user) { - validateNull(user); - this.user = user; - } -} diff --git a/src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java b/src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java deleted file mode 100644 index 0cfeb410..00000000 --- a/src/main/java/com/chooz/user/domain/UserOnboardingStepRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.chooz.user.domain; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface UserOnboardingStepRepository extends JpaRepository { - boolean existsByUserAndStep(User user, OnboardingStep step); -} diff --git a/src/main/java/com/chooz/user/domain/UserRepository.java b/src/main/java/com/chooz/user/domain/UserRepository.java index 80f5ad4a..f7db918b 100644 --- a/src/main/java/com/chooz/user/domain/UserRepository.java +++ b/src/main/java/com/chooz/user/domain/UserRepository.java @@ -9,11 +9,5 @@ @Repository public interface UserRepository extends JpaRepository { - @Query(""" - SELECT u - FROM User u - JOIN FETCH u.onboardingSteps - where u.id = :userId - """) - Optional findByIdFetchOnboardingSteps(@Param("userId") Long userId); + boolean existsByNickname(String nickName); } diff --git a/src/main/java/com/chooz/user/presentation/UserController.java b/src/main/java/com/chooz/user/presentation/UserController.java index 5a6cbbae..454d8091 100644 --- a/src/main/java/com/chooz/user/presentation/UserController.java +++ b/src/main/java/com/chooz/user/presentation/UserController.java @@ -19,21 +19,21 @@ public class UserController { @GetMapping("/{userId}") public ResponseEntity findUserInfo(@PathVariable("userId") Long userId) { - return ResponseEntity.ok(userService.findByIdFetchOnboardingSteps(userId)); + return ResponseEntity.ok(userService.findById(userId)); } @GetMapping("/me") public ResponseEntity findMyInfo( @AuthenticationPrincipal UserInfo userInfo ) { - return ResponseEntity.ok(userService.findByMeFetchOnboardingSteps(userInfo.userId())); + return ResponseEntity.ok(userService.findByMe(userInfo.userId())); } - @PostMapping("/onboarding") - public ResponseEntity findUserInfo( + @PatchMapping("/onboarding") + public ResponseEntity findUserInfo( @RequestBody OnboardingRequest request, @AuthenticationPrincipal UserInfo userInfo ) { - return ResponseEntity.ok(userService.completeStep(userInfo.userId(), request.step())); + return ResponseEntity.ok(userService.completeStep(userInfo.userId(), request)); } } diff --git a/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java b/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java index 9cbb2171..b1a11604 100644 --- a/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java +++ b/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java @@ -2,15 +2,18 @@ import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; -import com.chooz.user.domain.OnboardingStep; -import jakarta.validation.constraints.NotNull; +import com.chooz.user.domain.OnboardingStepType; + +import java.util.Map; public record OnboardingRequest( - @NotNull - OnboardingStep step + Map onboardingStep ) { public OnboardingRequest { - if (step != OnboardingStep.WELCOME_GUIDE && step != OnboardingStep.FIRST_VOTE) { + if (onboardingStep == null + || onboardingStep.isEmpty() + || onboardingStep.values().stream().noneMatch(Boolean.TRUE::equals) + ) { throw new BadRequestException(ErrorCode.INVALID_ONBOARDING_STEP); } } diff --git a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java index accdec49..626d0077 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java @@ -1,25 +1,35 @@ package com.chooz.user.presentation.dto; import com.chooz.user.domain.OnboardingStep; +import com.chooz.user.domain.OnboardingStepType; import com.chooz.user.domain.User; -import com.chooz.user.domain.UserOnboardingStep; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; public record UserInfoResponse( Long id, String nickname, String profileImageUrl, - List onboardingSteps, - boolean notification + boolean notification, + Map onboardingStep + ) { public static UserInfoResponse of(User user) { return new UserInfoResponse( user.getId(), user.getNickname(), user.getProfileUrl(), - user.getOnboardingSteps().stream().map(UserOnboardingStep::getStep).toList(), - user.isNotification() + user.isNotification(), + convertStepStatus(user.getOnboardingStep()) ); } + + private static Map convertStepStatus(OnboardingStep step) { + return Arrays.stream(OnboardingStepType.values()) + .collect(Collectors.toMap( + Enum::name, + stepType -> step != null && stepType.check(step) + )); + } } diff --git a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java index 00798b44..0bc4135c 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java @@ -1,26 +1,35 @@ package com.chooz.user.presentation.dto; import com.chooz.user.domain.OnboardingStep; -import com.chooz.user.domain.Role; +import com.chooz.user.domain.OnboardingStepType; import com.chooz.user.domain.User; -import com.chooz.user.domain.UserOnboardingStep; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; public record UserMyInfoResponse( Long id, String nickname, String profileImageUrl, - List onboardingSteps, - boolean notification + boolean notification, + Map onboardingStep + ) { public static UserMyInfoResponse of(User user) { return new UserMyInfoResponse( user.getId(), user.getNickname(), user.getProfileUrl(), - user.getOnboardingSteps().stream().map(UserOnboardingStep::getStep).toList(), - user.isNotification() + user.isNotification(), + convertStepStatus(user.getOnboardingStep()) ); } + + private static Map convertStepStatus(OnboardingStep step) { + return Arrays.stream(OnboardingStepType.values()) + .collect(Collectors.toMap( + Enum::name, + stepType -> step != null && stepType.check(step) + )); + } } diff --git a/src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java b/src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java new file mode 100644 index 00000000..0623f602 --- /dev/null +++ b/src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java @@ -0,0 +1,24 @@ +package com.chooz.support.fixture; + +import com.chooz.user.domain.Role; +import com.chooz.user.domain.User; + +import java.util.List; +import java.util.Map; + +public class UserFixture { + + public static User createDefaultUser() { + return createUserBuilder().build(); + } + + public static User.UserBuilder createUserBuilder() { + return User.builder() + .nickname("nickname") + .profileUrl("http://example.com/profile.png") + .notification(false) + .onboardingStep( + ) + + } +} From 13e917da6f6840c513d8310bf44b55f9e68df1f9 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Tue, 5 Aug 2025 21:23:33 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat=20:=20user=20onboarding=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=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 --- .../fixture/OnboardingStepFixture.java | 21 +-- .../chooz/support/fixture/UserFixture.java | 11 +- .../application/NicknameGeneratorTest.java | 8 +- .../user/application/UserServiceTest.java | 63 +++++-- .../java/com/chooz/user/domain/UserTest.java | 3 + .../user/presentation/UserControllerTest.java | 169 +++++++++++++++--- 6 files changed, 222 insertions(+), 53 deletions(-) diff --git a/src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java b/src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java index 0623f602..1023642b 100644 --- a/src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java +++ b/src/test/java/com/chooz/support/fixture/OnboardingStepFixture.java @@ -1,24 +1,17 @@ package com.chooz.support.fixture; -import com.chooz.user.domain.Role; +import com.chooz.user.domain.OnboardingStep; import com.chooz.user.domain.User; -import java.util.List; -import java.util.Map; +public class OnboardingStepFixture { -public class UserFixture { - - public static User createDefaultUser() { + public static OnboardingStep createDefaultOnboardingStep() { return createUserBuilder().build(); } - public static User.UserBuilder createUserBuilder() { - return User.builder() - .nickname("nickname") - .profileUrl("http://example.com/profile.png") - .notification(false) - .onboardingStep( - ) - + public static OnboardingStep.OnboardingStepBuilder createUserBuilder() { + return OnboardingStep.builder() + .welcomeGuide(false) + .firstVote(false); } } diff --git a/src/test/java/com/chooz/support/fixture/UserFixture.java b/src/test/java/com/chooz/support/fixture/UserFixture.java index 5f1e889e..d1277ba7 100644 --- a/src/test/java/com/chooz/support/fixture/UserFixture.java +++ b/src/test/java/com/chooz/support/fixture/UserFixture.java @@ -3,6 +3,11 @@ import com.chooz.user.domain.Role; import com.chooz.user.domain.User; +import java.util.List; +import java.util.Map; + +import static com.chooz.support.fixture.OnboardingStepFixture.createDefaultOnboardingStep; + public class UserFixture { public static User createDefaultUser() { @@ -12,8 +17,8 @@ public static User createDefaultUser() { public static User.UserBuilder createUserBuilder() { return User.builder() .nickname("nickname") - .profileUrl("http://example.com/profile.png") - .is_onboard(true) - .notification(false); + .profileUrl("https://cdn.chooz.com/default_profile.png") + .notification(false) + .onboardingStep(createDefaultOnboardingStep()); } } diff --git a/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java b/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java index 9006c251..9e7bfd3d 100644 --- a/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java +++ b/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java @@ -1,8 +1,6 @@ package com.chooz.user.application; -import com.chooz.user.domain.NicknameAdjective; -import com.chooz.user.domain.NicknameAdjectiveRepository; -import com.chooz.user.domain.Role; +import com.chooz.user.domain.*; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,6 +23,9 @@ class NicknameGeneratorTest { @Mock NicknameAdjectiveRepository nicknameAdjectiveRepository; + @Mock + UserRepository userRepository; + @Test @DisplayName("닉네임 생성 테스트") void generate() throws Exception { @@ -38,5 +39,4 @@ void generate() throws Exception { //then Assertions.assertThat(nickname).isEqualTo("호기심 많은 츄"); } - } diff --git a/src/test/java/com/chooz/user/application/UserServiceTest.java b/src/test/java/com/chooz/user/application/UserServiceTest.java index 92eee24e..d9700243 100644 --- a/src/test/java/com/chooz/user/application/UserServiceTest.java +++ b/src/test/java/com/chooz/user/application/UserServiceTest.java @@ -2,14 +2,16 @@ import com.chooz.support.IntegrationTest; import com.chooz.support.fixture.UserFixture; -import com.chooz.user.domain.NicknameAdjective; -import com.chooz.user.domain.NicknameAdjectiveRepository; -import com.chooz.user.domain.User; -import com.chooz.user.domain.UserRepository; +import com.chooz.user.domain.*; +import com.chooz.user.presentation.dto.OnboardingRequest; +import com.chooz.user.presentation.dto.UserInfoResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -27,24 +29,65 @@ class UserServiceTest extends IntegrationTest { UserService userService; @Test + @DisplayName("유저생성 테스트") void createUser() { // given - User user = User.create(null, "https://image.com/1"); + User user = User.create(null, "https://cdn.chooz.site/default_profile.png"); - for (int i = 0; i < 250; i++) { - nicknameAdjectiveRepository.save(new NicknameAdjective("호기심 많은 츄")); - nicknameAdjectiveRepository.save(new NicknameAdjective("배려 깊은 츄")); - } + nicknameAdjectiveRepository.save(new NicknameAdjective("호기심 많은")); + nicknameAdjectiveRepository.save(new NicknameAdjective("배려 깊은")); // when Long userId = userService.createUser(user.getNickname(), user.getProfileUrl()); Optional returnUser = userRepository.findById(userId); - // when then assertAll( () -> assertThat(returnUser.get().getNickname()).isNotNull(), () -> assertThat(returnUser.get().getNickname()).contains("츄") ); + } + @Test + @DisplayName("유저생성 닉넥임 중복 테스트") + void createUser_duplicateNickname() { + // given + User user = User.create(null, "https://cdn.chooz.site/default_profile.png"); + + nicknameAdjectiveRepository.save(new NicknameAdjective("호기심 많은")); + // when + Long userId1 = userService.createUser(user.getNickname(), user.getProfileUrl()); + Long userId2 = userService.createUser(user.getNickname(), user.getProfileUrl()); + + User returnUser1 = userRepository.findById(userId1).get(); + User returnUser2 = userRepository.findById(userId2).get(); + + // when then + assertAll( + () -> assertThat(returnUser1.getNickname()).isNotNull(), + () -> assertThat(returnUser1.getNickname()).contains("츄"), + () -> assertThat(returnUser1.getNickname()).isNotEqualTo(returnUser2.getNickname()) + ); + } + @Test + @DisplayName("온보딩 수행 테스트") + void usser_complete_onboarding_step() { + // given + User user = UserFixture.createDefaultUser(); + Long userId = userService.createUser(user.getNickname(), user.getProfileUrl()); + OnboardingRequest onboardingRequest = new OnboardingRequest( + Map.of( + OnboardingStepType.WELCOME_GUIDE, true, + OnboardingStepType.FIRST_VOTE, false + ) + ); + // when + userService.completeStep(userId, onboardingRequest); + OnboardingStep onboardingStep + = userRepository.findById(userId).get().getOnboardingStep(); + // then + assertAll( + () -> assertThat(onboardingStep.isWelcomeGuide()).isTrue(), + () -> assertThat(onboardingStep.isFirstVote()).isFalse() + ); } } diff --git a/src/test/java/com/chooz/user/domain/UserTest.java b/src/test/java/com/chooz/user/domain/UserTest.java index c132f8cf..28d849dd 100644 --- a/src/test/java/com/chooz/user/domain/UserTest.java +++ b/src/test/java/com/chooz/user/domain/UserTest.java @@ -1,8 +1,11 @@ package com.chooz.user.domain; +import com.chooz.user.application.NicknameGenerator; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; class UserTest { diff --git a/src/test/java/com/chooz/user/presentation/UserControllerTest.java b/src/test/java/com/chooz/user/presentation/UserControllerTest.java index 51178f8f..a96c4f5c 100644 --- a/src/test/java/com/chooz/user/presentation/UserControllerTest.java +++ b/src/test/java/com/chooz/user/presentation/UserControllerTest.java @@ -2,19 +2,23 @@ import com.chooz.support.RestDocsTest; import com.chooz.support.WithMockUserInfo; -import com.chooz.user.domain.Role; +import com.chooz.user.domain.OnboardingStepType; +import com.chooz.user.presentation.dto.OnboardingRequest; import com.chooz.user.presentation.dto.UserInfoResponse; import com.chooz.user.presentation.dto.UserMyInfoResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.payload.JsonFieldType.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -27,10 +31,20 @@ class UserControllerTest extends RestDocsTest { @DisplayName("유저 정보 조회") void findUserInfo() throws Exception { //given - UserInfoResponse response = new UserInfoResponse(1L, "nickname", "https://image.com/profile-image", true, false); + Map onboardingStep = Map.of( + "WELCOME_GUIDE", false, + "FIRST_VOTE", true + ); + UserInfoResponse response = new UserInfoResponse( + 1L, + "nickname", + "https://cdn.chooz.site/default_profile.png", + false, + onboardingStep + ); given(userService.findById(1L)) .willReturn(response); - + System.out.println(objectMapper.writeValueAsString(response)); //when then mockMvc.perform(RestDocumentationRequestBuilders.get("/users/{userId}", "1")) .andExpect(status().isOk()) @@ -40,11 +54,27 @@ void findUserInfo() throws Exception { parameterWithName("userId").description("유저 아이디") ), responseFields( - fieldWithPath("id").description("유저 아이디").type(NUMBER), - fieldWithPath("nickname").description("닉네임").type(STRING), - fieldWithPath("profileUrl").description("프로필 이미지 URL").type(STRING), - fieldWithPath("is_onboard").description("온보딩 유저 여부").type(BOOLEAN), - fieldWithPath("notification").description("알림 설정 여부").type(BOOLEAN) + fieldWithPath("id") + .description("유저 아이디") + .type(JsonFieldType.NUMBER), + fieldWithPath("nickname") + .description("닉네임") + .type(JsonFieldType.STRING), + fieldWithPath("profileImageUrl") + .description("프로필 이미지 URL") + .type(JsonFieldType.STRING), + fieldWithPath("notification") + .description("알림 설정 여부") + .type(JsonFieldType.BOOLEAN), + fieldWithPath("onboardingStep") + .description("유저 온보딩 단계") + .type(JsonFieldType.OBJECT), + fieldWithPath("onboardingStep.WELCOME_GUIDE") + .description("웰컴 가이드 완료 여부") + .type(JsonFieldType.BOOLEAN), + fieldWithPath("onboardingStep.FIRST_VOTE") + .description("첫 투표 완료 여부") + .type(JsonFieldType.BOOLEAN) ) )); } @@ -54,23 +84,118 @@ void findUserInfo() throws Exception { @DisplayName("본인 정보 조회") void findMe() throws Exception { //given - UserMyInfoResponse response = new UserMyInfoResponse(1L, "nickname", "https://image.com/profile-image", true, false); + Map onboardingStep = Map.of( + "WELCOME_GUIDE", false, + "FIRST_VOTE", true + ); + UserMyInfoResponse response = new UserMyInfoResponse( + 1L, + "nickname", + "https://cdn.chooz.site/default_profile.png", + false, + onboardingStep + ); given(userService.findByMe(1L)) .willReturn(response); //when then - mockMvc.perform(RestDocumentationRequestBuilders.get("/users/me") - .header(HttpHeaders.AUTHORIZATION, "Bearer access-token")) + mockMvc.perform(RestDocumentationRequestBuilders.get("/users/me")) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(response))) + .andDo(restDocs.document( + responseFields( + fieldWithPath("id") + .description("유저 아이디") + .type(JsonFieldType.NUMBER), + fieldWithPath("nickname") + .description("닉네임") + .type(JsonFieldType.STRING), + fieldWithPath("profileImageUrl") + .description("프로필 이미지 URL") + .type(JsonFieldType.STRING), + fieldWithPath("notification") + .description("알림 설정 여부") + .type(JsonFieldType.BOOLEAN), + fieldWithPath("onboardingStep") + .description("유저 온보딩 단계") + .type(JsonFieldType.OBJECT), + fieldWithPath("onboardingStep.WELCOME_GUIDE") + .description("웰컴 가이드 완료 여부") + .type(JsonFieldType.BOOLEAN), + fieldWithPath("onboardingStep.FIRST_VOTE") + .description("첫 투표 완료 여부") + .type(JsonFieldType.BOOLEAN) + ) + )); + } + @Test + @WithMockUserInfo + @DisplayName("온보딩 수행") + void completeStep () throws Exception { + // given + Map steps = Map.of( + OnboardingStepType.WELCOME_GUIDE, false, + OnboardingStepType.FIRST_VOTE, true + + ); + OnboardingRequest request = new OnboardingRequest(steps); + + Map responseSteps = Map.of( + "WELCOME_GUIDE", false, + "FIRST_VOTE", true + + ); + UserInfoResponse response = new UserInfoResponse( + 1L, + "nickname", + "https://cdn.chooz.site/default_profile.png", + false, + responseSteps + ); + + given(userService.completeStep(eq(1L), any(OnboardingRequest.class))) + .willReturn(response); + + // when & then + mockMvc.perform(RestDocumentationRequestBuilders.patch("/users/onboarding") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(content().json(objectMapper.writeValueAsString(response))) .andDo(restDocs.document( - requestHeaders(authorizationHeader()), + requestFields( + fieldWithPath("onboardingStep") + .description("온보딩 단계") + .type(JsonFieldType.OBJECT), + fieldWithPath("onboardingStep.WELCOME_GUIDE") + .description("웰컴 가이드 완료 여부") + .type(JsonFieldType.BOOLEAN), + fieldWithPath("onboardingStep.FIRST_VOTE") + .description("첫 투표 완료 여부") + .type(JsonFieldType.BOOLEAN) + ), responseFields( - fieldWithPath("id").description("유저 아이디").type(NUMBER), - fieldWithPath("nickname").description("닉네임").type(STRING), - fieldWithPath("profileImageUrl").description("프로필 이미지 URL").type(STRING), - fieldWithPath("is_onboard").description("온보딩 유저 여부").type(BOOLEAN), - fieldWithPath("notification").description("알림 설정 여부").type(BOOLEAN) + fieldWithPath("id") + .description("유저 아이디") + .type(JsonFieldType.NUMBER), + fieldWithPath("nickname") + .description("닉네임") + .type(JsonFieldType.STRING), + fieldWithPath("profileImageUrl") + .description("프로필 이미지 URL") + .type(JsonFieldType.STRING), + fieldWithPath("notification") + .description("알림 설정 여부") + .type(JsonFieldType.BOOLEAN), + fieldWithPath("onboardingStep") + .description("유저 온보딩 단계") + .type(JsonFieldType.OBJECT), + fieldWithPath("onboardingStep.WELCOME_GUIDE") + .description("웰컴 가이드 완료 여부") + .type(JsonFieldType.BOOLEAN), + fieldWithPath("onboardingStep.FIRST_VOTE") + .description("첫 투표 완료 여부") + .type(JsonFieldType.BOOLEAN) ) )); } From f8394fcca6767abcd4b7f4192b3c879540e1c7e1 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Tue, 5 Aug 2025 21:38:29 +0900 Subject: [PATCH 07/16] =?UTF-8?q?docs=20:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/users.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/docs/asciidoc/users.adoc b/src/docs/asciidoc/users.adoc index 6580e170..5742b2fd 100644 --- a/src/docs/asciidoc/users.adoc +++ b/src/docs/asciidoc/users.adoc @@ -11,5 +11,10 @@ operation::user-controller-test/find-user-info[snippets='http-request,curl-reque operation::user-controller-test/find-me[snippets='http-request,curl-request,request-headers,http-response,response-fields'] +[[온보딩-단계-완료]] +=== `PATCH` 온보딩 단계 완료 + +operation::user-controller-test/complete-step[snippets='http-request,curl-request,request-headers,request-fields,http-response,response-fields'] + [[본인-정보-조회]] === `GET` 내 정보 수정 (미구현) From d1b75b14fe62f21b8d2b9536e9a655151d69760c Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Tue, 5 Aug 2025 21:50:44 +0900 Subject: [PATCH 08/16] =?UTF-8?q?fix=20:=20import=EB=AC=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/chooz/user/application/UserService.java | 1 - src/main/java/com/chooz/user/domain/OnboardingStep.java | 1 - src/main/java/com/chooz/user/domain/User.java | 4 ---- src/main/java/com/chooz/user/domain/UserRepository.java | 4 ---- .../com/chooz/user/application/NicknameGeneratorTest.java | 2 -- src/test/java/com/chooz/user/application/UserServiceTest.java | 3 --- src/test/java/com/chooz/user/domain/UserTest.java | 3 --- .../java/com/chooz/user/presentation/UserControllerTest.java | 1 - 8 files changed, 19 deletions(-) diff --git a/src/main/java/com/chooz/user/application/UserService.java b/src/main/java/com/chooz/user/application/UserService.java index f1940365..6ce692b4 100644 --- a/src/main/java/com/chooz/user/application/UserService.java +++ b/src/main/java/com/chooz/user/application/UserService.java @@ -10,7 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/com/chooz/user/domain/OnboardingStep.java b/src/main/java/com/chooz/user/domain/OnboardingStep.java index a6e24a88..0681b940 100644 --- a/src/main/java/com/chooz/user/domain/OnboardingStep.java +++ b/src/main/java/com/chooz/user/domain/OnboardingStep.java @@ -6,7 +6,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import static com.chooz.common.util.Validator.validateNull; @Getter @Entity diff --git a/src/main/java/com/chooz/user/domain/User.java b/src/main/java/com/chooz/user/domain/User.java index 449eb604..6f09abd4 100644 --- a/src/main/java/com/chooz/user/domain/User.java +++ b/src/main/java/com/chooz/user/domain/User.java @@ -6,12 +6,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; -import static com.chooz.common.util.Validator.validateNull; - @Getter @Entity @Table(name = "users") diff --git a/src/main/java/com/chooz/user/domain/UserRepository.java b/src/main/java/com/chooz/user/domain/UserRepository.java index f7db918b..efa357e1 100644 --- a/src/main/java/com/chooz/user/domain/UserRepository.java +++ b/src/main/java/com/chooz/user/domain/UserRepository.java @@ -1,12 +1,8 @@ package com.chooz.user.domain; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface UserRepository extends JpaRepository { boolean existsByNickname(String nickName); diff --git a/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java b/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java index 9e7bfd3d..ca1879a6 100644 --- a/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java +++ b/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java @@ -10,8 +10,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.Optional; - -import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) diff --git a/src/test/java/com/chooz/user/application/UserServiceTest.java b/src/test/java/com/chooz/user/application/UserServiceTest.java index d9700243..ecfeb07b 100644 --- a/src/test/java/com/chooz/user/application/UserServiceTest.java +++ b/src/test/java/com/chooz/user/application/UserServiceTest.java @@ -4,14 +4,11 @@ import com.chooz.support.fixture.UserFixture; import com.chooz.user.domain.*; import com.chooz.user.presentation.dto.OnboardingRequest; -import com.chooz.user.presentation.dto.UserInfoResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/com/chooz/user/domain/UserTest.java b/src/test/java/com/chooz/user/domain/UserTest.java index 28d849dd..c132f8cf 100644 --- a/src/test/java/com/chooz/user/domain/UserTest.java +++ b/src/test/java/com/chooz/user/domain/UserTest.java @@ -1,11 +1,8 @@ package com.chooz.user.domain; -import com.chooz.user.application.NicknameGenerator; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; class UserTest { diff --git a/src/test/java/com/chooz/user/presentation/UserControllerTest.java b/src/test/java/com/chooz/user/presentation/UserControllerTest.java index a96c4f5c..76a51ba1 100644 --- a/src/test/java/com/chooz/user/presentation/UserControllerTest.java +++ b/src/test/java/com/chooz/user/presentation/UserControllerTest.java @@ -12,7 +12,6 @@ import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.payload.JsonFieldType; -import java.util.HashMap; import java.util.Map; import static org.mockito.ArgumentMatchers.any; From 2bb4eac917c23de595be089ef5fb0ce225c985f1 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 10 Aug 2025 00:34:02 +0900 Subject: [PATCH 09/16] =?UTF-8?q?refactor=20:=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=B2=88=ED=98=B8?= =?UTF-8?q?=EB=B6=80=EC=97=AC=20=EB=A1=9C=EC=A7=81=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=A0=9C=EA=B1=B0=20=EB=B2=84=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/application/NicknameGenerator.java | 41 +++++++++++++++---- .../com/chooz/user/domain/UserRepository.java | 13 +++++- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/chooz/user/application/NicknameGenerator.java b/src/main/java/com/chooz/user/application/NicknameGenerator.java index c6531559..003803b3 100644 --- a/src/main/java/com/chooz/user/application/NicknameGenerator.java +++ b/src/main/java/com/chooz/user/application/NicknameGenerator.java @@ -1,10 +1,19 @@ package com.chooz.user.application; +import com.chooz.common.exception.BadRequestException; +import com.chooz.common.exception.ErrorCode; import com.chooz.user.domain.NicknameAdjectiveRepository; import com.chooz.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + @Component @RequiredArgsConstructor public class NicknameGenerator { @@ -13,17 +22,33 @@ public class NicknameGenerator { private final UserRepository userRepository; public String generate() { - String nickName = nicknameAdjectiveRepository.findRandomNicknameAdjective() + String prefix = nicknameAdjectiveRepository.findRandomNicknameAdjective() .map(adjective -> adjective.getAdjective() + " 츄") .orElse("숨겨진 츄"); - return checkDuplicateNickname(nickName); + return makeNickname(prefix); + } + private String makeNickname(String prefix) { + List nickNames = userRepository.findNicknamesByPrefix(prefix); + Set usedSuffixes = getUsedSuffixes(prefix, nickNames); + return findUsableNickname(prefix, usedSuffixes); + } + private Set getUsedSuffixes(String prefix, List nickNames) { + Set usedSuffixes = new TreeSet<>(BigInteger::compareTo); + for(String nickName : nickNames) { + String suffix = nickName.substring(prefix.length()); + if(suffix.isEmpty()) { + usedSuffixes.add(BigInteger.ZERO); + }else{ + usedSuffixes.add(new BigInteger(suffix)); + } + } + return usedSuffixes; } - private String checkDuplicateNickname(String nickName) { - int suffix = 1; - while (userRepository.existsByNickname(nickName)){ - nickName = nickName + suffix; - suffix++; + private String findUsableNickname(String prefix, Set usedSuffixes) { + BigInteger suffix = BigInteger.ZERO; + while (usedSuffixes.contains(suffix)) { + suffix = suffix.add(BigInteger.ONE); } - return nickName; + return suffix.signum() == 0 ? prefix : prefix + suffix; } } diff --git a/src/main/java/com/chooz/user/domain/UserRepository.java b/src/main/java/com/chooz/user/domain/UserRepository.java index efa357e1..c6758943 100644 --- a/src/main/java/com/chooz/user/domain/UserRepository.java +++ b/src/main/java/com/chooz/user/domain/UserRepository.java @@ -1,9 +1,20 @@ package com.chooz.user.domain; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface UserRepository extends JpaRepository { - boolean existsByNickname(String nickName); + @Query(""" + SELECT u.nickname + FROM User u + WHERE u.nickname + LIKE CONCAT(:prefix, '%') + """) + List findNicknamesByPrefix(@Param("prefix") String prefix); + } From fa063889c1b72bae75a43862cfa33c6085094180 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 10 Aug 2025 01:17:38 +0900 Subject: [PATCH 10/16] =?UTF-8?q?test=20:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=85=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chooz/support/fixture/UserFixture.java | 3 ++ .../user/application/UserServiceTest.java | 43 ++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/chooz/support/fixture/UserFixture.java b/src/test/java/com/chooz/support/fixture/UserFixture.java index d1277ba7..33333e0c 100644 --- a/src/test/java/com/chooz/support/fixture/UserFixture.java +++ b/src/test/java/com/chooz/support/fixture/UserFixture.java @@ -13,6 +13,9 @@ public class UserFixture { public static User createDefaultUser() { return createUserBuilder().build(); } + public static User createUserWithNickname (String nickname) { + return createUserBuilder().nickname(nickname).build(); + } public static User.UserBuilder createUserBuilder() { return User.builder() diff --git a/src/test/java/com/chooz/user/application/UserServiceTest.java b/src/test/java/com/chooz/user/application/UserServiceTest.java index ecfeb07b..34ce800d 100644 --- a/src/test/java/com/chooz/user/application/UserServiceTest.java +++ b/src/test/java/com/chooz/user/application/UserServiceTest.java @@ -22,6 +22,9 @@ class UserServiceTest extends IntegrationTest { @Autowired NicknameAdjectiveRepository nicknameAdjectiveRepository; + @Autowired + NicknameGenerator nicknameGenerator; + @Autowired UserService userService; @@ -43,6 +46,7 @@ void createUser() { () -> assertThat(returnUser.get().getNickname()).contains("츄") ); } + @Test @DisplayName("유저생성 닉넥임 중복 테스트") void createUser_duplicateNickname() { @@ -62,12 +66,41 @@ void createUser_duplicateNickname() { assertAll( () -> assertThat(returnUser1.getNickname()).isNotNull(), () -> assertThat(returnUser1.getNickname()).contains("츄"), - () -> assertThat(returnUser1.getNickname()).isNotEqualTo(returnUser2.getNickname()) + () -> assertThat(returnUser1.getNickname()).isNotEqualTo(returnUser2.getNickname()), + () -> assertThat(returnUser1.getNickname()).isEqualTo("호기심 많은 츄"), + () -> assertThat(returnUser2.getNickname()).isEqualTo("호기심 많은 츄1") + ); + } + @Test + @DisplayName("유저생성 닉넥임 사용가능한 가장 작은 suffix 선택 테스트") + void createUser_minSuffix() { + // given + nicknameAdjectiveRepository.save(new NicknameAdjective("호기심 많은")); + User user = UserFixture.createUserWithNickname(nicknameGenerator.generate()); + User returnUser = userRepository.save(user); + User user1 = UserFixture.createUserWithNickname(nicknameGenerator.generate()); + User returnUser1 = userRepository.save(user1); + User user2 = UserFixture.createUserWithNickname(nicknameGenerator.generate()); + User returnUser2 = userRepository.save(user2); + + // when + userRepository.delete(returnUser1); + User user3 = UserFixture.createUserWithNickname(nicknameGenerator.generate()); + User returnUser3 = userRepository.save(user3); + + // when then + assertAll( + () -> assertThat(returnUser3.getNickname()).isNotNull(), + () -> assertThat(returnUser3.getNickname()).contains("츄"), + () -> assertThat(returnUser.getNickname()).isEqualTo("호기심 많은 츄"), + () -> assertThat(returnUser2.getNickname()).isEqualTo("호기심 많은 츄2"), + () -> assertThat(returnUser3.getNickname()).isEqualTo("호기심 많은 츄1") ); } + @Test @DisplayName("온보딩 수행 테스트") - void usser_complete_onboarding_step() { + void user_complete_onboarding_step() { // given User user = UserFixture.createDefaultUser(); Long userId = userService.createUser(user.getNickname(), user.getProfileUrl()); @@ -78,9 +111,9 @@ void usser_complete_onboarding_step() { ) ); // when - userService.completeStep(userId, onboardingRequest); - OnboardingStep onboardingStep - = userRepository.findById(userId).get().getOnboardingStep(); + userService.completeStep(userId, onboardingRequest); + OnboardingStep onboardingStep + = userRepository.findById(userId).get().getOnboardingStep(); // then assertAll( () -> assertThat(onboardingStep.isWelcomeGuide()).isTrue(), From 71f51bc037f04448f8a15c79f3c311547106c51e Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 10 Aug 2025 01:42:31 +0900 Subject: [PATCH 11/16] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chooz/support/fixture/UserFixture.java | 4 - .../user/application/UserServiceTest.java | 73 ++++++++----------- 2 files changed, 31 insertions(+), 46 deletions(-) diff --git a/src/test/java/com/chooz/support/fixture/UserFixture.java b/src/test/java/com/chooz/support/fixture/UserFixture.java index 33333e0c..16de869c 100644 --- a/src/test/java/com/chooz/support/fixture/UserFixture.java +++ b/src/test/java/com/chooz/support/fixture/UserFixture.java @@ -1,11 +1,7 @@ package com.chooz.support.fixture; -import com.chooz.user.domain.Role; import com.chooz.user.domain.User; -import java.util.List; -import java.util.Map; - import static com.chooz.support.fixture.OnboardingStepFixture.createDefaultOnboardingStep; public class UserFixture { diff --git a/src/test/java/com/chooz/user/application/UserServiceTest.java b/src/test/java/com/chooz/user/application/UserServiceTest.java index 34ce800d..37b894f2 100644 --- a/src/test/java/com/chooz/user/application/UserServiceTest.java +++ b/src/test/java/com/chooz/user/application/UserServiceTest.java @@ -9,7 +9,6 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.Map; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -28,22 +27,26 @@ class UserServiceTest extends IntegrationTest { @Autowired UserService userService; + private void saveNickNameAdjective(String... adjectives) { + for(String adjective : adjectives){ + nicknameAdjectiveRepository.save(new NicknameAdjective(adjective)); + } + } + private User saveUser(){ + User user = UserFixture.createUserWithNickname(nicknameGenerator.generate()); + return userRepository.save(user); + } @Test @DisplayName("유저생성 테스트") void createUser() { // given - User user = User.create(null, "https://cdn.chooz.site/default_profile.png"); + saveNickNameAdjective("호기심 많은", "배려 깊은"); + User user = saveUser(); - nicknameAdjectiveRepository.save(new NicknameAdjective("호기심 많은")); - nicknameAdjectiveRepository.save(new NicknameAdjective("배려 깊은")); - - // when - Long userId = userService.createUser(user.getNickname(), user.getProfileUrl()); - Optional returnUser = userRepository.findById(userId); // when then assertAll( - () -> assertThat(returnUser.get().getNickname()).isNotNull(), - () -> assertThat(returnUser.get().getNickname()).contains("츄") + () -> assertThat(user.getNickname()).isNotNull(), + () -> assertThat(user.getNickname()).contains("츄") ); } @@ -51,50 +54,37 @@ void createUser() { @DisplayName("유저생성 닉넥임 중복 테스트") void createUser_duplicateNickname() { // given - User user = User.create(null, "https://cdn.chooz.site/default_profile.png"); - - nicknameAdjectiveRepository.save(new NicknameAdjective("호기심 많은")); - - // when - Long userId1 = userService.createUser(user.getNickname(), user.getProfileUrl()); - Long userId2 = userService.createUser(user.getNickname(), user.getProfileUrl()); - - User returnUser1 = userRepository.findById(userId1).get(); - User returnUser2 = userRepository.findById(userId2).get(); + saveNickNameAdjective("호기심 많은"); + User user = saveUser(); + User user2 = saveUser(); // when then assertAll( - () -> assertThat(returnUser1.getNickname()).isNotNull(), - () -> assertThat(returnUser1.getNickname()).contains("츄"), - () -> assertThat(returnUser1.getNickname()).isNotEqualTo(returnUser2.getNickname()), - () -> assertThat(returnUser1.getNickname()).isEqualTo("호기심 많은 츄"), - () -> assertThat(returnUser2.getNickname()).isEqualTo("호기심 많은 츄1") + () -> assertThat(user.getNickname()).isNotNull(), + () -> assertThat(user.getNickname()).contains("츄"), + () -> assertThat(user.getNickname()).isNotEqualTo(user2.getNickname()), + () -> assertThat(user.getNickname()).isEqualTo("호기심 많은 츄"), + () -> assertThat(user2.getNickname()).isEqualTo("호기심 많은 츄1") ); } @Test @DisplayName("유저생성 닉넥임 사용가능한 가장 작은 suffix 선택 테스트") void createUser_minSuffix() { // given - nicknameAdjectiveRepository.save(new NicknameAdjective("호기심 많은")); - User user = UserFixture.createUserWithNickname(nicknameGenerator.generate()); - User returnUser = userRepository.save(user); - User user1 = UserFixture.createUserWithNickname(nicknameGenerator.generate()); - User returnUser1 = userRepository.save(user1); - User user2 = UserFixture.createUserWithNickname(nicknameGenerator.generate()); - User returnUser2 = userRepository.save(user2); + saveNickNameAdjective("호기심 많은"); + User user = saveUser(); + User user1 = saveUser(); + User user2 = saveUser(); + userRepository.delete(user1); // when - userRepository.delete(returnUser1); - User user3 = UserFixture.createUserWithNickname(nicknameGenerator.generate()); - User returnUser3 = userRepository.save(user3); + User user3 = saveUser(); // when then assertAll( - () -> assertThat(returnUser3.getNickname()).isNotNull(), - () -> assertThat(returnUser3.getNickname()).contains("츄"), - () -> assertThat(returnUser.getNickname()).isEqualTo("호기심 많은 츄"), - () -> assertThat(returnUser2.getNickname()).isEqualTo("호기심 많은 츄2"), - () -> assertThat(returnUser3.getNickname()).isEqualTo("호기심 많은 츄1") + () -> assertThat(user.getNickname()).isEqualTo("호기심 많은 츄"), + () -> assertThat(user2.getNickname()).isEqualTo("호기심 많은 츄2"), + () -> assertThat(user3.getNickname()).isEqualTo("호기심 많은 츄1") ); } @@ -102,8 +92,7 @@ void createUser_minSuffix() { @DisplayName("온보딩 수행 테스트") void user_complete_onboarding_step() { // given - User user = UserFixture.createDefaultUser(); - Long userId = userService.createUser(user.getNickname(), user.getProfileUrl()); + Long userId = saveUser().getId(); OnboardingRequest onboardingRequest = new OnboardingRequest( Map.of( OnboardingStepType.WELCOME_GUIDE, true, From 47e01be065d0c9d07f21b22d222cc2955fddefba Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 10 Aug 2025 01:43:18 +0900 Subject: [PATCH 12/16] =?UTF-8?q?fix=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=20=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/chooz/user/application/NicknameGenerator.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/chooz/user/application/NicknameGenerator.java b/src/main/java/com/chooz/user/application/NicknameGenerator.java index 003803b3..158ee26e 100644 --- a/src/main/java/com/chooz/user/application/NicknameGenerator.java +++ b/src/main/java/com/chooz/user/application/NicknameGenerator.java @@ -1,15 +1,10 @@ package com.chooz.user.application; -import com.chooz.common.exception.BadRequestException; -import com.chooz.common.exception.ErrorCode; import com.chooz.user.domain.NicknameAdjectiveRepository; import com.chooz.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; - -import java.math.BigDecimal; import java.math.BigInteger; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; From 51e578ca8149dcac43485e5b49be43c4023a8139 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 10 Aug 2025 01:49:55 +0900 Subject: [PATCH 13/16] =?UTF-8?q?fix=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/chooz/common/exception/ErrorCode.java | 1 - src/main/java/com/chooz/user/application/UserService.java | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/main/java/com/chooz/common/exception/ErrorCode.java b/src/main/java/com/chooz/common/exception/ErrorCode.java index dcb120aa..e7e7ed24 100644 --- a/src/main/java/com/chooz/common/exception/ErrorCode.java +++ b/src/main/java/com/chooz/common/exception/ErrorCode.java @@ -46,7 +46,6 @@ public enum ErrorCode { DUPLICATE_POLL_CHOICE("복수 투표의 경우 중복된 선택지가 있으면 안 됨"), NOT_POST_POLL_CHOICE_ID("게시글의 투표 선택지가 아님"), INVALID_ONBOARDING_STEP("잘못된 온보딩 단계"), - ONBOARDING_NOT_INITIALIZED("온보딩이 초기화 되지 않음."), //401 EXPIRED_TOKEN("토큰이 만료됐습니다."), diff --git a/src/main/java/com/chooz/user/application/UserService.java b/src/main/java/com/chooz/user/application/UserService.java index 6ce692b4..752baee4 100644 --- a/src/main/java/com/chooz/user/application/UserService.java +++ b/src/main/java/com/chooz/user/application/UserService.java @@ -51,9 +51,6 @@ public UserMyInfoResponse findByMe(Long userId) { public UserInfoResponse completeStep(Long userId, OnboardingRequest onboardingRequest) { User user = userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); - if (user.getOnboardingStep() == null) { - throw new BadRequestException(ErrorCode.ONBOARDING_NOT_INITIALIZED); - } UpdateOnboardingStep(user, onboardingRequest); return UserInfoResponse.of(userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND))); From d24b7f10552e0a10495facb41c4a29111c9cfc93 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sun, 10 Aug 2025 02:05:31 +0900 Subject: [PATCH 14/16] =?UTF-8?q?fix=20:=20error=20code=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 --- src/main/java/com/chooz/common/exception/ErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/chooz/common/exception/ErrorCode.java b/src/main/java/com/chooz/common/exception/ErrorCode.java index 523e9138..6154c662 100644 --- a/src/main/java/com/chooz/common/exception/ErrorCode.java +++ b/src/main/java/com/chooz/common/exception/ErrorCode.java @@ -46,6 +46,7 @@ public enum ErrorCode { DUPLICATE_POLL_CHOICE("복수 투표의 경우 중복된 선택지가 있으면 안 됨"), NOT_POST_POLL_CHOICE_ID("게시글의 투표 선택지가 아님"), ONLY_SELF_CAN_CLOSE("작성자 마감의 경우, SELF 마감 방식만이 마감 가능합니다."), + INVALID_ONBOARDING_STEP("유효하지 않은 온보딩 단계."), //401 EXPIRED_TOKEN("토큰이 만료됐습니다."), From 5c5c3cfdadd9747a671a2c769c58b6798423a00c Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Mon, 18 Aug 2025 01:45:01 +0900 Subject: [PATCH 15/16] =?UTF-8?q?fix=20:=20dto=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=EB=8B=A8=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chooz/user/application/UserService.java | 3 +++ .../user/presentation/UserController.java | 3 ++- .../presentation/dto/OnboardingRequest.java | 17 ++++---------- .../user/application/UserServiceTest.java | 23 +++++++++++++++++++ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/chooz/user/application/UserService.java b/src/main/java/com/chooz/user/application/UserService.java index 752baee4..b5c9baba 100644 --- a/src/main/java/com/chooz/user/application/UserService.java +++ b/src/main/java/com/chooz/user/application/UserService.java @@ -49,6 +49,9 @@ public UserMyInfoResponse findByMe(Long userId) { @Transactional public UserInfoResponse completeStep(Long userId, OnboardingRequest onboardingRequest) { + if (onboardingRequest.onboardingStep().values().stream().noneMatch(Boolean.TRUE::equals)) { + throw new BadRequestException(ErrorCode.INVALID_ONBOARDING_STEP); + } User user = userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); UpdateOnboardingStep(user, onboardingRequest); diff --git a/src/main/java/com/chooz/user/presentation/UserController.java b/src/main/java/com/chooz/user/presentation/UserController.java index 454d8091..2c1f009d 100644 --- a/src/main/java/com/chooz/user/presentation/UserController.java +++ b/src/main/java/com/chooz/user/presentation/UserController.java @@ -5,6 +5,7 @@ import com.chooz.user.presentation.dto.OnboardingRequest; import com.chooz.user.presentation.dto.UserInfoResponse; import com.chooz.user.presentation.dto.UserMyInfoResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -31,7 +32,7 @@ public ResponseEntity findMyInfo( @PatchMapping("/onboarding") public ResponseEntity findUserInfo( - @RequestBody OnboardingRequest request, + @Valid @RequestBody OnboardingRequest request, @AuthenticationPrincipal UserInfo userInfo ) { return ResponseEntity.ok(userService.completeStep(userInfo.userId(), request)); diff --git a/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java b/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java index b1a11604..e39c3ae5 100644 --- a/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java +++ b/src/main/java/com/chooz/user/presentation/dto/OnboardingRequest.java @@ -1,20 +1,13 @@ package com.chooz.user.presentation.dto; -import com.chooz.common.exception.BadRequestException; -import com.chooz.common.exception.ErrorCode; import com.chooz.user.domain.OnboardingStepType; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.Map; public record OnboardingRequest( + @NotNull + @Size(min = 1) Map onboardingStep -) { - public OnboardingRequest { - if (onboardingStep == null - || onboardingStep.isEmpty() - || onboardingStep.values().stream().noneMatch(Boolean.TRUE::equals) - ) { - throw new BadRequestException(ErrorCode.INVALID_ONBOARDING_STEP); - } - } -} +) {} diff --git a/src/test/java/com/chooz/user/application/UserServiceTest.java b/src/test/java/com/chooz/user/application/UserServiceTest.java index 37b894f2..f73ea1b0 100644 --- a/src/test/java/com/chooz/user/application/UserServiceTest.java +++ b/src/test/java/com/chooz/user/application/UserServiceTest.java @@ -1,5 +1,7 @@ package com.chooz.user.application; +import com.chooz.common.exception.BadRequestException; +import com.chooz.common.exception.ErrorCode; import com.chooz.support.IntegrationTest; import com.chooz.support.fixture.UserFixture; import com.chooz.user.domain.*; @@ -11,6 +13,7 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; class UserServiceTest extends IntegrationTest { @@ -109,4 +112,24 @@ void user_complete_onboarding_step() { () -> assertThat(onboardingStep.isFirstVote()).isFalse() ); } + @Test + @DisplayName("온보딩 요청 예외 테스트") + void user_complete_onboarding_step_exception() { + // given + Long userId = saveUser().getId(); + OnboardingRequest onboardingRequest = new OnboardingRequest( + Map.of( + OnboardingStepType.WELCOME_GUIDE, false, + OnboardingStepType.FIRST_VOTE, false + ) + ); + + // when then + assertThatThrownBy( + () -> userService.completeStep(userId, onboardingRequest)) + .isInstanceOf(BadRequestException.class) + .extracting("errorCode") + .isEqualTo(ErrorCode.INVALID_ONBOARDING_STEP + ); + } } From 184bca4b2456a3ce03031052c3948f38fc9041a7 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Mon, 18 Aug 2025 02:12:07 +0900 Subject: [PATCH 16/16] =?UTF-8?q?fix=20:=20import=20=EB=AC=B8=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/chooz/user/application/UserService.java | 4 +++- .../java/com/chooz/user/domain/OnboardingStep.java | 6 +++++- src/main/java/com/chooz/user/domain/User.java | 10 +++++++++- .../com/chooz/user/presentation/UserController.java | 7 ++++++- .../chooz/user/presentation/dto/UserInfoResponse.java | 3 ++- .../user/presentation/dto/UserMyInfoResponse.java | 3 ++- .../chooz/user/application/NicknameGeneratorTest.java | 4 +++- .../com/chooz/user/application/UserServiceTest.java | 9 ++++++--- .../chooz/user/presentation/UserControllerTest.java | 4 +++- 9 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/chooz/user/application/UserService.java b/src/main/java/com/chooz/user/application/UserService.java index b5c9baba..e87845d8 100644 --- a/src/main/java/com/chooz/user/application/UserService.java +++ b/src/main/java/com/chooz/user/application/UserService.java @@ -2,7 +2,9 @@ import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; -import com.chooz.user.domain.*; +import com.chooz.user.domain.UserRepository; +import com.chooz.user.domain.OnboardingStepRepository; +import com.chooz.user.domain.User; import com.chooz.user.presentation.dto.OnboardingRequest; import com.chooz.user.presentation.dto.UserInfoResponse; import com.chooz.user.presentation.dto.UserMyInfoResponse; diff --git a/src/main/java/com/chooz/user/domain/OnboardingStep.java b/src/main/java/com/chooz/user/domain/OnboardingStep.java index 0681b940..e51e61c7 100644 --- a/src/main/java/com/chooz/user/domain/OnboardingStep.java +++ b/src/main/java/com/chooz/user/domain/OnboardingStep.java @@ -1,7 +1,11 @@ package com.chooz.user.domain; import com.chooz.common.domain.BaseEntity; -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/chooz/user/domain/User.java b/src/main/java/com/chooz/user/domain/User.java index 6f09abd4..b1d7cbb7 100644 --- a/src/main/java/com/chooz/user/domain/User.java +++ b/src/main/java/com/chooz/user/domain/User.java @@ -1,7 +1,15 @@ package com.chooz.user.domain; import com.chooz.common.domain.BaseEntity; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/chooz/user/presentation/UserController.java b/src/main/java/com/chooz/user/presentation/UserController.java index 2c1f009d..dbe458a7 100644 --- a/src/main/java/com/chooz/user/presentation/UserController.java +++ b/src/main/java/com/chooz/user/presentation/UserController.java @@ -9,7 +9,12 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor diff --git a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java index 626d0077..2f7d4315 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserInfoResponse.java @@ -4,7 +4,8 @@ import com.chooz.user.domain.OnboardingStepType; import com.chooz.user.domain.User; -import java.util.*; +import java.util.Arrays; +import java.util.Map; import java.util.stream.Collectors; public record UserInfoResponse( diff --git a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java index 0bc4135c..d3981641 100644 --- a/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java +++ b/src/main/java/com/chooz/user/presentation/dto/UserMyInfoResponse.java @@ -4,7 +4,8 @@ import com.chooz.user.domain.OnboardingStepType; import com.chooz.user.domain.User; -import java.util.*; +import java.util.Arrays; +import java.util.Map; import java.util.stream.Collectors; public record UserMyInfoResponse( diff --git a/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java b/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java index ca1879a6..dac5974e 100644 --- a/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java +++ b/src/test/java/com/chooz/user/application/NicknameGeneratorTest.java @@ -1,6 +1,8 @@ package com.chooz.user.application; -import com.chooz.user.domain.*; +import com.chooz.user.domain.NicknameAdjective; +import com.chooz.user.domain.NicknameAdjectiveRepository; +import com.chooz.user.domain.UserRepository; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/chooz/user/application/UserServiceTest.java b/src/test/java/com/chooz/user/application/UserServiceTest.java index f73ea1b0..3ae39673 100644 --- a/src/test/java/com/chooz/user/application/UserServiceTest.java +++ b/src/test/java/com/chooz/user/application/UserServiceTest.java @@ -4,14 +4,17 @@ import com.chooz.common.exception.ErrorCode; import com.chooz.support.IntegrationTest; import com.chooz.support.fixture.UserFixture; -import com.chooz.user.domain.*; +import com.chooz.user.domain.NicknameAdjective; +import com.chooz.user.domain.NicknameAdjectiveRepository; +import com.chooz.user.domain.OnboardingStep; +import com.chooz.user.domain.OnboardingStepType; +import com.chooz.user.domain.User; +import com.chooz.user.domain.UserRepository; import com.chooz.user.presentation.dto.OnboardingRequest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; - import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/chooz/user/presentation/UserControllerTest.java b/src/test/java/com/chooz/user/presentation/UserControllerTest.java index 76a51ba1..6d913283 100644 --- a/src/test/java/com/chooz/user/presentation/UserControllerTest.java +++ b/src/test/java/com/chooz/user/presentation/UserControllerTest.java @@ -17,7 +17,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;