Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 온보딩 정보를 저장, 조회하는 api를 추가한다. #19

Merged
merged 4 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
}