Skip to content

Commit

Permalink
feat: 온보딩 정보를 저장, 조회하는 api를 추가한다. (#19)
Browse files Browse the repository at this point in the history
* feat: 온보딩 정보를 저장, 조회하는 api를 추가한다.

* chore: swagger operation 추가

* feat: 이미 프로필이 존재하는 경우, 중복 생성되지 않도록 한다.

* test: add test code
  • Loading branch information
rlarltj authored Aug 6, 2024
1 parent d2c7ef8 commit b397ebf
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.dnd.accompany.domain.user.api;

import com.dnd.accompany.domain.auth.dto.jwt.JwtAuthentication;
import com.dnd.accompany.domain.user.dto.CreateUserProfileRequest;
import com.dnd.accompany.domain.user.service.UserProfileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Onboarding")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/profiles")
public class UserProfileController {

private final UserProfileService userProfileService;

@Operation(summary = "온보딩 정보 저장")
@PostMapping
public void createUserProfile(@AuthenticationPrincipal JwtAuthentication user,
@Valid CreateUserProfileRequest createUserProfileRequest
) {
userProfileService.createUserProfile(user.getId(), createUserProfileRequest);
}

@Operation(summary = "온보딩 여부 조회")
@GetMapping("/exist")
public ResponseEntity<Boolean> existUserProfile(@AuthenticationPrincipal JwtAuthentication user) {
boolean result = userProfileService.existByUserId(user.getId());
return ResponseEntity.ok(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.dnd.accompany.domain.user.dto;

import com.dnd.accompany.domain.user.entity.enums.FoodPreference;
import com.dnd.accompany.domain.user.entity.enums.Gender;
import com.dnd.accompany.domain.user.entity.enums.TravelPreference;
import com.dnd.accompany.domain.user.entity.enums.TravelStyle;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public record CreateUserProfileRequest(
int birthYear,

@NotNull
Gender gender,

@NotEmpty
List<TravelPreference> travelPreferences,

@NotEmpty
List<TravelStyle> travelStyles,

@NotEmpty
List<FoodPreference> foodPreferences
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.dnd.accompany.domain.user.exception;

import com.dnd.accompany.global.common.exception.BusinessException;
import com.dnd.accompany.global.common.response.ErrorCode;

public class UserProfileAlreadyExistsException extends BusinessException {
public UserProfileAlreadyExistsException(ErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.dnd.accompany.domain.user.service;

import com.dnd.accompany.domain.user.dto.CreateUserProfileRequest;
import com.dnd.accompany.domain.user.entity.UserProfile;
import com.dnd.accompany.domain.user.exception.UserProfileAlreadyExistsException;
import com.dnd.accompany.domain.user.infrastructure.UserProfileRepository;
import com.dnd.accompany.global.common.response.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserProfileService {

private final UserProfileRepository userProfileRepository;

@Transactional
public void createUserProfile(Long userId, CreateUserProfileRequest createUserProfileRequest) {
validateDuplicateProfile(userId);

UserProfile userProfile = UserProfile.builder()
.userId(userId)
.birthYear(createUserProfileRequest.birthYear())
.gender(createUserProfileRequest.gender())
.travelPreferences(createUserProfileRequest.travelPreferences())
.travelStyles(createUserProfileRequest.travelStyles())
.foodPreferences(createUserProfileRequest.foodPreferences())
.build();

userProfileRepository.save(userProfile);
}

@Transactional(readOnly = true)
public boolean existByUserId(Long userId) {
return userProfileRepository.existsById(userId);
}

private void validateDuplicateProfile(Long userId) {
if (userProfileRepository.existsById(userId)) {
throw new UserProfileAlreadyExistsException(ErrorCode.PROFILE_ALREADY_EXISTS);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public enum ErrorCode {
INVALID_PROVIDER(MatripConstant.BAD_REQUEST, "LOGIN-001", "유효하지 않은 로그인 수단입니다."),
INVALID_OAUTH_TOKEN(MatripConstant.BAD_REQUEST, "LOGIN-002", "유효하지 않은 OAuth 토큰입니다."),

// ---- 프로필 ---- //
PROFILE_ALREADY_EXISTS(MatripConstant.BAD_REQUEST, "PROFILE-001", "이미 프로필 정보가 존재합니다."),

// ---- 네트워크 ---- //
HTTP_CLIENT_REQUEST_FAILED(MatripConstant.INTERNAL_SERVER_ERROR, "NETWORK-001", "서버 요청에 실패하였습니다.");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.dnd.accompany.domain.user.service;

import com.dnd.accompany.domain.user.dto.CreateUserProfileRequest;
import com.dnd.accompany.domain.user.entity.UserProfile;
import com.dnd.accompany.domain.user.entity.enums.FoodPreference;
import com.dnd.accompany.domain.user.entity.enums.Gender;
import com.dnd.accompany.domain.user.entity.enums.TravelPreference;
import com.dnd.accompany.domain.user.entity.enums.TravelStyle;
import com.dnd.accompany.domain.user.exception.UserProfileAlreadyExistsException;
import com.dnd.accompany.domain.user.infrastructure.UserProfileRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static com.dnd.accompany.domain.user.entity.enums.FoodPreference.*;
import static com.dnd.accompany.domain.user.entity.enums.TravelPreference.*;
import static com.dnd.accompany.domain.user.entity.enums.TravelStyle.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserProfileServiceTest {

@Mock
private UserProfileRepository userProfileRepository;

@InjectMocks
private UserProfileService userProfileService;

@DisplayName("유저 프로필을 생성할 때")
@Nested
class profile {

private Long userId = 100L;
private CreateUserProfileRequest createUserProfileRequest;

@BeforeEach
void setup() {
createUserProfileRequest = new CreateUserProfileRequest(
2000,
Gender.MALE,
List.of(DRAWN_TO, PUBLIC_MONEY, QUICKLY, LEISURELY),
List.of(ACTIVITY, HEALING, CAFE_TOUR, SHOPPING),
List.of(MEAT, RICE, COFFEE, FAST_FOOD)
);
}

@DisplayName("신규 생성인 경우 정상 생성된다.")
@Test
void success() {
//given
given(userProfileRepository.existsById(anyLong()))
.willReturn(false);

//when
userProfileService.createUserProfile(userId, createUserProfileRequest);

//then
verify(userProfileRepository).save(any(UserProfile.class));
}

@DisplayName("이미 프로필이 존재하는 경우 예외가 발생한다.")
@Test
void fail() {
//given
given(userProfileRepository.existsById(anyLong()))
.willReturn(true);

//when & then
assertThrows(UserProfileAlreadyExistsException.class,
() -> userProfileService.createUserProfile(userId, createUserProfileRequest));
}
}
}

0 comments on commit b397ebf

Please sign in to comment.