diff --git a/build.gradle b/build.gradle index c7c7a25..a33ed66 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,10 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + + // Swagger dependency + implementation 'io.springfox:springfox-boot-starter:3.0.0' + implementation 'io.springfox:springfox-swagger-ui:3.0.0' } tasks.named('test') { diff --git a/src/main/java/com/diareat/diareat/user/controller/UserController.java b/src/main/java/com/diareat/diareat/user/controller/UserController.java index d7be5f3..ce6c3f5 100644 --- a/src/main/java/com/diareat/diareat/user/controller/UserController.java +++ b/src/main/java/com/diareat/diareat/user/controller/UserController.java @@ -1,9 +1,91 @@ package com.diareat.diareat.user.controller; +import com.diareat.diareat.user.dto.*; +import com.diareat.diareat.user.service.UserService; +import com.diareat.diareat.util.api.ApiResponse; +import com.diareat.diareat.util.api.ResponseCode; +import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RestController; +import net.bytebuddy.implementation.bind.annotation.Empty; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +@Api(tags = "1. User") @RequiredArgsConstructor @RestController +@RequestMapping("/api/user") public class UserController { + + private final UserService userService; + + // 회원정보 저장 + @Operation(summary = "[회원가입] 회원정보 저장", description = "신규 회원정보를 저장합니다.") + @PostMapping("/save") + public ApiResponse saveUser(CreateUserDto createUserDto) { + return ApiResponse.success(userService.saveUser(createUserDto), ResponseCode.USER_CREATE_SUCCESS.getMessage()); + } + + // 회원 기본정보 조회 + @Operation(summary = "[프로필] 회원 기본정보 조회", description = "회원 기본정보를 조회합니다.") + @GetMapping("{userId}/info/simple/") + public ApiResponse getSimpleUserInfo(@PathVariable Long userId) { + return ApiResponse.success(userService.getSimpleUserInfo(userId), ResponseCode.USER_CREATE_SUCCESS.getMessage()); + } + + // 회원정보 조회 + @Operation(summary = "[프로필] 회원 정보 조회", description = "회원 정보를 조회합니다.") + @GetMapping("{userId}/info") + public ApiResponse getUserInfo(@PathVariable Long userId) { + return ApiResponse.success(userService.getUserInfo(userId), ResponseCode.USER_CREATE_SUCCESS.getMessage()); + } + + // 회원정보 수정 + @Operation(summary = "[프로필] 회원 정보 수정", description = "회원 정보를 수정합니다.") + @PutMapping("/update") + public ApiResponse updateUserInfo(UpdateUserDto updateUserDto) { + userService.updateUserInfo(updateUserDto); + return ApiResponse.success(null, ResponseCode.USER_UPDATE_SUCCESS.getMessage()); + } + + // 회원 기준섭취량 조회 + @Operation(summary = "[프로필] 회원 기준섭취량 조회", description = "회원 기준섭취량을 조회합니다.") + @GetMapping("{userId}/nutrition") + public ApiResponse getUserNutrition(@PathVariable Long userId) { + return ApiResponse.success(userService.getUserNutrition(userId), ResponseCode.USER_READ_SUCCESS.getMessage()); + } + + // 회원 기준섭취량 직접 수정 + @Operation(summary = "[프로필] 회원 기준섭취량 직접 수정", description = "회원 기준섭취량을 직접 수정합니다.") + @PutMapping("{userId}/nutrition") + public ApiResponse updateUserNutrition(UpdateUserNutritionDto updateUserNutritionDto) { + userService.updateBaseNutrition(updateUserNutritionDto); + return ApiResponse.success(null, ResponseCode.USER_UPDATE_SUCCESS.getMessage()); + } + + // 회원의 친구 검색 결과 조회 + @Operation(summary = "[주간 랭킹] 회원의 친구 검색 결과 조회", description = "회원의 친구 검색 결과를 조회합니다.") + @GetMapping("{userId}/search/{name}") + public ApiResponse> searchUser(@PathVariable Long userId, @RequestParam String name) { + return ApiResponse.success(userService.searchUser(userId, name), ResponseCode.USER_SEARCH_SUCCESS.getMessage()); + } + + // 실제 팔로잉 유저들의 점수 계산하여 랭킹 형태로 반환하는 API: FoodService에서 계산할지 UserService에서 FoodRepository를 콜해서 처리할지 협의 필요 + + // 회원이 특정 회원 팔로우 + @Operation(summary = "[주간 랭킹] 회원이 특정 회원 팔로우", description = "회원이 특정 회원을 팔로우합니다.") + @PostMapping("{userId}/follow/{followId}") + public ApiResponse followUser(@PathVariable Long userId, @PathVariable Long followId) { + userService.followUser(userId, followId); + return ApiResponse.success(null, ResponseCode.USER_UPDATE_SUCCESS.getMessage()); + } + + // 회원이 특정 회원 팔로우 취소 + @Operation(summary = "[주간 랭킹] 회원이 특정 회원 팔로우 취소", description = "회원이 특정 회원을 팔로우 취소합니다.") + @DeleteMapping("{userId}/follow/{followId}") + public ApiResponse unfollowUser(@PathVariable Long userId, @PathVariable Long followId) { + userService.unfollowUser(userId, followId); + return ApiResponse.success(null, ResponseCode.USER_UPDATE_SUCCESS.getMessage()); + } } diff --git a/src/main/java/com/diareat/diareat/user/repository/UserRepository.java b/src/main/java/com/diareat/diareat/user/repository/UserRepository.java index 2c45f0a..743ec3a 100644 --- a/src/main/java/com/diareat/diareat/user/repository/UserRepository.java +++ b/src/main/java/com/diareat/diareat/user/repository/UserRepository.java @@ -7,4 +7,5 @@ public interface UserRepository extends JpaRepository { List findAllByNameContaining(String name); // 회원이 팔로우를 위해 검색한 유저 목록 조회 + boolean existsByName(String name); // 회원가입 시 닉네임 중복 확인 } diff --git a/src/main/java/com/diareat/diareat/user/service/UserService.java b/src/main/java/com/diareat/diareat/user/service/UserService.java index 2ce3bee..fe49358 100644 --- a/src/main/java/com/diareat/diareat/user/service/UserService.java +++ b/src/main/java/com/diareat/diareat/user/service/UserService.java @@ -27,6 +27,8 @@ public class UserService { public Long saveUser(CreateUserDto createUserDto) { BaseNutrition baseNutrition = BaseNutrition.createNutrition(2000, 300, 80, 80); // BaseNutrition baseNutrition = BaseNutrition.createNutrition(createUserDto.getGender(), createUserDto.getAge(), createUserDto.getHeight(), createUserDto.getWeight()); + if (userRepository.existsByName(createUserDto.getName())) + throw new UserException(ResponseCode.USER_ALREADY_EXIST); User user = User.createUser(createUserDto.getName(), createUserDto.getKeyCode(), createUserDto.getHeight(), createUserDto.getWeight(), createUserDto.getGender(), createUserDto.getAge(), baseNutrition); return userRepository.save(user).getId(); } @@ -89,6 +91,9 @@ public List searchUser(Long hostId, String name) { public void followUser(Long userId, Long followId) { validateUser(userId); validateUser(followId); + // 이미 팔로우 중인 경우 + if (followRepository.existsByFromUserAndToUser(userId, followId)) + throw new UserException(ResponseCode.FOLLOWED_ALREADY); followRepository.save(Follow.makeFollow(userId, followId)); } @@ -97,11 +102,23 @@ public void followUser(Long userId, Long followId) { public void unfollowUser(Long userId, Long unfollowId) { validateUser(userId); validateUser(unfollowId); + // 이미 팔로우 취소한 경우 + if (followRepository.existsByFromUserAndToUser(userId, unfollowId)) + throw new UserException(ResponseCode.UNFOLLOWED_ALREADY); followRepository.delete(Follow.makeFollow(userId, unfollowId)); } + // 회원의 팔로우 목록 조회 (현재 외부 Dto 변환은 Food에서 위임받아 진행할지 협의하지 않았기에 일단 User 리스트로 반환) + @Transactional(readOnly = true) + public List getFollowList(Long userId) { + validateUser(userId); + List users = followRepository.findAllByFromUser(userId); + users.add(getUserById(userId)); // 자기 자신도 랭킹에 포함 + return users; + } + private void validateUser(Long userId) { - if(!userRepository.existsById(userId)) + if (!userRepository.existsById(userId)) throw new UserException(ResponseCode.USER_NOT_FOUND); } diff --git a/src/main/java/com/diareat/diareat/util/api/ResponseCode.java b/src/main/java/com/diareat/diareat/util/api/ResponseCode.java index 450a24a..0f1574b 100644 --- a/src/main/java/com/diareat/diareat/util/api/ResponseCode.java +++ b/src/main/java/com/diareat/diareat/util/api/ResponseCode.java @@ -16,15 +16,42 @@ public enum ResponseCode { FORBIDDEN(HttpStatus.FORBIDDEN, false, "권한이 없습니다."), // 404 Not Found - USER_NOT_FOUND(HttpStatus.NOT_FOUND, false,"사용자를 찾을 수 없습니다."), - FOOD_NOT_FOUND(HttpStatus.NOT_FOUND, false,"음식을 찾을 수 없습니다."), - FAVORITE_NOT_FOUND(HttpStatus.NOT_FOUND, false,"즐겨찾기를 찾을 수 없습니다."), + USER_NOT_FOUND(HttpStatus.NOT_FOUND, false, "사용자를 찾을 수 없습니다."), + FOOD_NOT_FOUND(HttpStatus.NOT_FOUND, false, "음식을 찾을 수 없습니다."), + FAVORITE_NOT_FOUND(HttpStatus.NOT_FOUND, false, "즐겨찾기를 찾을 수 없습니다."), // 405 Method Not Allowed - METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, false,"허용되지 않은 메소드입니다."), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, false, "허용되지 않은 메소드입니다."), + + // 409 Conflict + USER_ALREADY_EXIST(HttpStatus.CONFLICT, false, "이미 존재하는 닉네임입니다."), + FOLLOWED_ALREADY(HttpStatus.CONFLICT, false, "이미 팔로우한 사용자입니다."), + UNFOLLOWED_ALREADY(HttpStatus.CONFLICT, false, "이미 언팔로우한 사용자입니다."), + FOOD_ALREADY_EXIST(HttpStatus.CONFLICT, false, "이미 즐겨찾기에 존재하는 음식입니다."), // 500 Internal Server Error - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, false,"서버에 오류가 발생하였습니다."); + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, false, "서버에 오류가 발생하였습니다."), + + // 200 OK + USER_READ_SUCCESS(HttpStatus.CREATED, true, "사용자 정보 조회 성공"), + USER_UPDATE_SUCCESS(HttpStatus.OK, true, "사용자 정보 수정 성공"), + USER_SEARCH_SUCCESS(HttpStatus.OK, true, "사용자 검색 성공"), + USER_FOLLOW_SUCCESS(HttpStatus.OK, true, "사용자 팔로우 성공"), + USER_UNFOLLOW_SUCCESS(HttpStatus.OK, true, "사용자 언팔로우 성공"), + + FOOD_READ_SUCCESS(HttpStatus.OK, true, "음식 정보 조회 성공"), + FOOD_UPDATE_SUCCESS(HttpStatus.OK, true, "음식 정보 수정 성공"), + FOOD_DELETE_SUCCESS(HttpStatus.OK, true, "음식 정보 삭제 성공"), + FOOD_FAVORITE_READ_SUCCESS(HttpStatus.OK, true, "즐겨찾기 음식 조회 성공"), + FOOD_FAVORITE_UPDATE_SUCCESS(HttpStatus.OK, true, "즐겨찾기 음식 수정 성공"), + FOOD_FAVORITE_DELETE_SUCCESS(HttpStatus.OK, true, "즐겨찾기 음식 삭제 성공"), + + + // 201 Created + USER_CREATE_SUCCESS(HttpStatus.CREATED, true, "사용자 생성 성공"), + FOOD_CREATE_SUCCESS(HttpStatus.CREATED, true, "음식 생성 성공"), + FOOD_FAVORITE_CREATE_SUCCESS(HttpStatus.OK, true, "즐겨찾기 음식 생성 성공"); + private final HttpStatus httpStatus; private final Boolean success; @@ -33,4 +60,4 @@ public enum ResponseCode { public int getHttpStatusCode() { return httpStatus.value(); } - } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3e18c96..61a7a3e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,5 @@ # db spring.profiles.include = db + +# swagger +spring.mvc.pathmatch.matching-strategy=ant_path_matcher \ No newline at end of file