diff --git a/build.gradle b/build.gradle index 1e84446..f7db427 100644 --- a/build.gradle +++ b/build.gradle @@ -45,8 +45,6 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' implementation group: 'org.springframework.data', name: 'spring-data-jpa', version: '3.4.3' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '3.4.3' - runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' - implementation 'org.mariadb.jdbc:mariadb-java-client:3.3.1' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' @@ -62,6 +60,7 @@ dependencies { testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + runtimeOnly 'com.mysql:mysql-connector-j' } tasks.named('test') { diff --git a/src/main/java/com/project/growfit/domain/Diet/controller/DietController.java b/src/main/java/com/project/growfit/domain/Diet/controller/DietController.java index 3dc237f..36f81f4 100644 --- a/src/main/java/com/project/growfit/domain/Diet/controller/DietController.java +++ b/src/main/java/com/project/growfit/domain/Diet/controller/DietController.java @@ -3,151 +3,151 @@ import com.project.growfit.domain.Diet.dto.request.AddDietRequestDto; import com.project.growfit.domain.Diet.dto.request.UpdateDietRequestDto; import com.project.growfit.domain.Diet.dto.request.UpdateFoodListRequestDto; +import com.project.growfit.domain.Diet.dto.response.*; import com.project.growfit.domain.Diet.entity.DietState; import com.project.growfit.domain.Diet.entity.Sticker; import com.project.growfit.domain.Diet.service.DietService; +import com.project.growfit.global.response.ResultCode; import com.project.growfit.global.response.ResultResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.List; + +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/api/diet") -@Tag(name = "Diet API", description = "식단일지 관련 API") +@Tag(name = "식단 관련 API", description = "식단일지 관련 API입니다.") public class DietController { private final DietService dietService; @Operation(summary = "음식 검색", description = "키워드로 음식을 검색합니다.") @GetMapping("/food/search") - public ResponseEntity searchFoods(@Parameter(description = "검색 키워드", example = "닭") @RequestParam String keyword, - @Parameter(description = "페이지 번호", example = "0") @RequestParam(defaultValue = "0") int page, - @Parameter(description = "페이지 크기", example = "10") @RequestParam(defaultValue = "10") int size) { - ResultResponse resultResponse = dietService.searchFoods(keyword, page, size); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponse> searchFoods(@Parameter(description = "검색 키워드", example = "닭") @RequestParam @NotBlank(message = "검색 키워드를 입력해주세요.") String keyword, + @Parameter(description = "페이지 번호", example = "0") @RequestParam(defaultValue = "0") @Min(0) int page, + @Parameter(description = "페이지 크기", example = "10") @RequestParam(defaultValue = "10") @Min(1) int size) { + + List dtos = dietService.searchFoods(keyword, page, size); + + return dtos.isEmpty() + ? ResultResponse.of(ResultCode.DIET_SEARCH_RESULT_EMPTY, dtos) + : ResultResponse.of(ResultCode.DIET_SEARCH_SUCCESS, dtos); } @Operation(summary = "음식 상세 조회", description = "특정 음식의 상세 정보를 조회합니다.") @GetMapping("/food/{foodId}") - public ResponseEntity getFoodDetail(@Parameter(description = "음식 ID", example = "1") + public ResultResponse getFoodDetail(@Parameter(description = "음식 ID", example = "1") @PathVariable Long foodId) { - ResultResponse resultResponse = dietService.getFoodDetail(foodId); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + FoodResponseDto dto = dietService.getFoodDetail(foodId); + return ResultResponse.of(ResultCode.DIET_DETAIL_RETRIEVAL_SUCCESS, dto); } @Operation(summary = "식단 추가", description = "식단을 추가합니다.") @PostMapping(value = "/food") - public ResponseEntity addDiet(@Valid @RequestBody AddDietRequestDto request) { - ResultResponse resultResponse = dietService.addDiet(request); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponseaddDiet(@Valid @RequestBody AddDietRequestDto request) { + DietBasicDto dto = dietService.addDiet(request); + + return ResultResponse.of(ResultCode.DIET_ADD_SUCCESS, dto); } @Operation(summary = "식단 수정", description = "식단을 수정합니다.") - @PutMapping("/food/{dietId}") - public ResponseEntity updateDiet(@PathVariable Long dietId, + @PatchMapping("/food/{dietId}") + public ResultResponse updateDiet(@PathVariable Long dietId, @Valid @RequestBody UpdateDietRequestDto request){ - ResultResponse resultResponse = dietService.updateDiet(dietId, request); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); - } + DietBasicDto dto = dietService.updateDiet(dietId, request); - @Operation(summary = "식단 시간 수정", description = "식단의 섭취 시간을 수정합니다.") - @PatchMapping("/food/{dietId}/time") - public ResponseEntity updateDietTime(@PathVariable Long dietId, - @RequestParam String updateTime) { - ResultResponse resultResponse = dietService.updateDietTime(dietId, updateTime); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.DIET_EDIT_SUCCESS, dto); } @Operation(summary = "식단 제거", description = "식단을 제거합니다.") @DeleteMapping("/food/{dietId}") - public ResponseEntity deleteDiet(@PathVariable Long dietId) { - ResultResponse resultResponse = dietService.deleteDiet(dietId); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponse deleteDiet(@PathVariable Long dietId) { + DietBasicDto dto = dietService.deleteDiet(dietId); + + return ResultResponse.of(ResultCode.DIET_DELETE_SUCCESS, dto); } @Operation(summary = "식단 상세 조회", description = "식단 상세 정보를 조회합니다.") @GetMapping("/food/info/{dietId}") - public ResponseEntity getDietDetail(@PathVariable Long dietId) { - ResultResponse resultResponse = dietService.getDietDetail(dietId); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); - } + public ResultResponse getDietDetail(@PathVariable Long dietId) { + DietResponseDto dto = dietService.getDietDetail(dietId); - @Operation(summary = "아이 식단 사진 추가", description = "아이가 저장된 식단에 사진을 추가합니다.") - @PostMapping(value = "/food/{dietId}/photo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadDietPhoto(@PathVariable Long dietId, - @RequestPart MultipartFile image) { - ResultResponse resultResponse = dietService.uploadPhoto(dietId, image); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.DIET_DETAIL_RETRIEVAL_SUCCESS, dto); } - @Operation(summary = "아이 식단 섭취 여부 등록", description = "식단 섭취 여부(MATCH/UNMATCH)를 등록합니다. 식단을 어길 시 UNMATCH, 식단을 지킬 시 MATCH") - @PatchMapping("/food/{dietId}/state") - public ResponseEntity updateDietState(@PathVariable Long dietId, - @RequestParam DietState dietState) { - ResultResponse result = dietService.updateDietState(dietId, dietState); - return ResponseEntity.ok(result); + @Operation(summary = "식단 완료 제출", description = "식단 사진과 식단 상태(MATCH/UNMATCH)를 함께 제출합니다.") + @PostMapping(value = "/food/{dietId}/submit", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResultResponse submitDiet( + @PathVariable Long dietId, + @RequestPart(name = "image", required = false) MultipartFile image, + @RequestPart(name = "dietState") DietState dietState) { + + DietBasicDto dto = dietService.submitDiet(dietId, image, dietState); + return ResultResponse.of(ResultCode.DIET_SUBMIT_SUCCESS, dto); } @Operation(summary = "식단 불이행 시 영양 정보 직접 입력", description = "아이 식단을 지키지 못한 경우 음식 리스트를 수정합니다.") @PutMapping("/food/{dietId}/override") - public ResponseEntity overrideDietNutrition(@PathVariable Long dietId, - @Valid @RequestBody UpdateFoodListRequestDto dto) { - ResultResponse resultResponse = dietService.overrideDietNutrition(dietId, dto); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponse overrideDietNutrition(@PathVariable Long dietId, + @Valid @RequestBody UpdateFoodListRequestDto request) { + DietBasicDto dto = dietService.overrideDietNutrition(dietId, request); + + return ResultResponse.of(ResultCode.DIET_OVERRIDE_SUCCESS, dto); } @Operation(summary = "월별 식단 요약 조회", description = "월별 식단 작성 여부와 스티커 남김 여부를 조회합니다.") @GetMapping("/daily/calendar") - public ResponseEntity getCalendarOverview(@Parameter(description = "조회할 월 (yyyy-MM)", example = "2025-04") + public ResultResponse getCalendarOverview(@Parameter(description = "조회할 월 (yyyy-MM)", example = "2025-04") @RequestParam("month") String month) { - ResultResponse resultResponse = dietService.getMonthlyStickersByParent(month); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + MonthlyStickerResponseDto dto = dietService.getMonthlyStickersByParent(month); + return dto.monthlyStickers().isEmpty() + ? ResultResponse.of(ResultCode.DIET_DATE_EMPTY, dto) + : ResultResponse.of(ResultCode.CALENDAR_OVERVIEW_SUCCESS, dto); } @Operation(summary = "날짜별 식단 조회", description = "선택한 날짜의 식단을 조회합니다.") @GetMapping("/daily") - public ResponseEntity getDailyDietByDate(@Parameter(description = "조회할 날짜", example = "2025-04-10") + public ResultResponse getDailyDietByDate(@Parameter(description = "조회할 날짜", example = "2025-04-10") @RequestParam("date") String date) { - ResultResponse resultResponse = dietService.getDailyDietByDate(date); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + DailyDietResponseDto dto = dietService.getDailyDietByDate(date); + return ResultResponse.of(ResultCode.DAILY_DIET_RETRIEVAL_SUCCESS, dto); } @Operation(summary = "스티커 업데이트", description = "식단에 스티커를 추가합니다.") @PostMapping("/daily/{dailyDietId}/sticker") - public ResponseEntity markSticker(@PathVariable Long dailyDietId, - @RequestParam Sticker sticker) { - ResultResponse resultResponse = dietService.markSticker(dailyDietId, sticker); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); - } - - @Operation(summary = "아이 식단 사진 삭제", description = "아이가 등록한 식단 사진을 삭제합니다.") - @DeleteMapping("/food/{dietId}/photo") - public ResponseEntity deleteDietPhoto(@PathVariable Long dietId) { - ResultResponse resultResponse = dietService.deletePhoto(dietId); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponse markSticker(@PathVariable Long dailyDietId, + @RequestParam Sticker sticker) { + DailyDietResponseDto dto = dietService.markSticker(dailyDietId, sticker); + return ResultResponse.of(ResultCode.STICKER_MARK_SUCCESS, dto); } @Operation(summary = "스티커 수정", description = "기존 스티커를 수정합니다.") @PatchMapping("/daily/{dailyDietId}/sticker") - public ResponseEntity updateSticker(@PathVariable Long dailyDietId, + public ResultResponse updateSticker(@PathVariable Long dailyDietId, @RequestParam Sticker sticker) { - ResultResponse result = dietService.updateSticker(dailyDietId, sticker); - return ResponseEntity.status(HttpStatus.OK).body(result); + DailyDietResponseDto dto = dietService.updateSticker(dailyDietId, sticker); + return ResultResponse.of(ResultCode.STICKER_UPDATE_SUCCESS, dto); } @Operation(summary = "스티커 삭제", description = "등록된 스티커를 삭제합니다.") @DeleteMapping("/daily/{dailyDietId}/sticker") - public ResponseEntity deleteSticker(@PathVariable Long dailyDietId) { - ResultResponse resultResponse = dietService.deleteSticker(dailyDietId); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponse deleteSticker(@PathVariable Long dailyDietId) { + DailyDietResponseDto dto = dietService.deleteSticker(dailyDietId); + return ResultResponse.of(ResultCode.STICKER_DELETE_SUCCESS, dto); } diff --git a/src/main/java/com/project/growfit/domain/Diet/controller/DietSetController.java b/src/main/java/com/project/growfit/domain/Diet/controller/DietSetController.java index b6afe58..eebb9e0 100644 --- a/src/main/java/com/project/growfit/domain/Diet/controller/DietSetController.java +++ b/src/main/java/com/project/growfit/domain/Diet/controller/DietSetController.java @@ -1,12 +1,17 @@ package com.project.growfit.domain.Diet.controller; import com.project.growfit.domain.Diet.dto.request.SaveDietSetRequestDto; +import com.project.growfit.domain.Diet.dto.response.DietSetBasicDto; +import com.project.growfit.domain.Diet.dto.response.DietSetDetailResponseDto; +import com.project.growfit.domain.Diet.dto.response.DietSetResponseDto; import com.project.growfit.domain.Diet.service.DietSetService; +import com.project.growfit.global.response.ResultCode; import com.project.growfit.global.response.ResultResponse; 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.data.domain.Page; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -14,7 +19,7 @@ @RestController @RequestMapping("/api/diet/set") -@Tag(name = "DietSet API", description = "식단 세트 API") +@Tag(name = "식단세트 관련 API", description = "식단세트 관련 API입니다.") @RequiredArgsConstructor public class DietSetController { @@ -22,38 +27,42 @@ public class DietSetController { @Operation(summary = "식단 세트 저장", description = "사용자가 식단 세트를 저장합니다.") @PostMapping - public ResponseEntity saveDietSet(@Valid @RequestBody SaveDietSetRequestDto dto) { - ResultResponse resultResponse = dietSetService.saveDietSet(dto); + public ResultResponse saveDietSet(@Valid @RequestBody SaveDietSetRequestDto request) { + DietSetBasicDto dto = dietSetService.saveDietSet(request); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); - } - - @Operation(summary = "모든 식단 세트 조회", description = "사용자가 저장한 모든 식단 세트를 페이지 단위로 조회합니다.") - @GetMapping - public ResponseEntity getDietSetList(@RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size) { - return ResponseEntity.ok(dietSetService.getAllDietSets(page, size)); - } - - @Operation(summary = "식단 세트 상세 조회", description = "사용자의 식단 세트의 상세 내용을 조회합니다.") - @GetMapping("{dietSetId}") - public ResponseEntity getDietSetDetail(@PathVariable Long dietSetId) { - return ResponseEntity.ok(dietSetService.getDietSetDetail(dietSetId)); + return ResultResponse.of(ResultCode.DIET_SET_SAVE_SUCCESS, dto); } @Operation(summary = "식단 세트 수정", description = "기존 식단 세트를 수정합니다.") @PutMapping("/{dietSetId}") - public ResponseEntity updateDietSet( + public ResultResponse updateDietSet( @PathVariable Long dietSetId, - @Valid @RequestBody SaveDietSetRequestDto dto) { - ResultResponse resultResponse = dietSetService.updateDietSet(dietSetId, dto); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + @Valid @RequestBody SaveDietSetRequestDto request) { + DietSetBasicDto dto = dietSetService.updateDietSet(dietSetId, request); + return ResultResponse.of(ResultCode.DIET_SET_EDIT_SUCCESS, dto); } @Operation(summary = "식단 세트 삭제", description = "식단 세트를 삭제합니다.") @DeleteMapping("/{dietSetId}") - public ResponseEntity deleteDietSet(@PathVariable Long dietSetId) { - ResultResponse resultResponse = dietSetService.deleteDietSet(dietSetId); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponse deleteDietSet(@PathVariable Long dietSetId) { + DietSetBasicDto dto = dietSetService.deleteDietSet(dietSetId); + return ResultResponse.of(ResultCode.DIET_SET_DELETE_SUCCESS, dto); + } + + @Operation(summary = "모든 식단 세트 조회", description = "사용자가 저장한 모든 식단 세트를 페이지 단위로 조회합니다.") + @GetMapping + public ResultResponse> getDietSetList(@RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + Page dto = dietSetService.getAllDietSets(page, size); + + return ResultResponse.of(ResultCode.DIET_SET_LIST_SUCCESS, dto); + } + + @Operation(summary = "식단 세트 상세 조회", description = "사용자의 식단 세트의 상세 내용을 조회합니다.") + @GetMapping("{dietSetId}") + public ResultResponse getDietSetDetail(@PathVariable Long dietSetId) { + DietSetDetailResponseDto dto = dietSetService.getDietSetDetail(dietSetId); + + return ResultResponse.of(ResultCode.DIET_SET_RETRIEVAL_SUCCESS, dto); } } \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/Diet/dto/request/UpdateDietRequestDto.java b/src/main/java/com/project/growfit/domain/Diet/dto/request/UpdateDietRequestDto.java index ae4bb2d..982daa3 100644 --- a/src/main/java/com/project/growfit/domain/Diet/dto/request/UpdateDietRequestDto.java +++ b/src/main/java/com/project/growfit/domain/Diet/dto/request/UpdateDietRequestDto.java @@ -9,16 +9,13 @@ @Schema(description = "식단 수정 요청 DTO") public record UpdateDietRequestDto( - @NotBlank(message = "시간을 입력해주세요.") @Schema(description = "식사 시간 (HH:mm)", example = "08:30") String eatTime, - @NotNull(message = "식사 종류를 입력해주세요.") @Schema(description = "식사 종류", example = "BREAKFAST") MealType mealType, @Schema(description = "수정 음식 리스트") - @NotNull(message = "음식을 입력해주세요.") @Size(min = 1, message = "음식은 최소 한 개 이상 입력해주세요.") List foodList ) {} \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/Diet/dto/response/DietBasicDto.java b/src/main/java/com/project/growfit/domain/Diet/dto/response/DietBasicDto.java new file mode 100644 index 0000000..d484863 --- /dev/null +++ b/src/main/java/com/project/growfit/domain/Diet/dto/response/DietBasicDto.java @@ -0,0 +1,21 @@ +package com.project.growfit.domain.Diet.dto.response; + +import com.project.growfit.domain.Diet.entity.Diet; +import com.project.growfit.domain.Diet.entity.DietState; +import com.project.growfit.domain.Diet.entity.MealType; + +public record DietBasicDto( + Long dietId, + Long childId, + MealType mealType, + String time, + DietState state +) { + public static DietBasicDto toDto(Diet diet) { + return new DietBasicDto(diet.getId(), + diet.getChild().getId(), + diet.getMealType(), + diet.getTime().toString(), + diet.getState()); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/Diet/dto/response/DietSetBasicDto.java b/src/main/java/com/project/growfit/domain/Diet/dto/response/DietSetBasicDto.java new file mode 100644 index 0000000..e6b2fb0 --- /dev/null +++ b/src/main/java/com/project/growfit/domain/Diet/dto/response/DietSetBasicDto.java @@ -0,0 +1,17 @@ +package com.project.growfit.domain.Diet.dto.response; + +import com.project.growfit.domain.Diet.entity.DietSet; + +public record DietSetBasicDto( + Long dietSetId, + String setName, + double totalCalorie +) { + public static DietSetBasicDto toDto(DietSet set) { + return new DietSetBasicDto( + set.getId(), + set.getSetName(), + set.getTotalCalorie() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/Diet/dto/response/MonthlyStickerResponseDto.java b/src/main/java/com/project/growfit/domain/Diet/dto/response/MonthlyStickerResponseDto.java index e21471f..3a9028a 100644 --- a/src/main/java/com/project/growfit/domain/Diet/dto/response/MonthlyStickerResponseDto.java +++ b/src/main/java/com/project/growfit/domain/Diet/dto/response/MonthlyStickerResponseDto.java @@ -12,4 +12,8 @@ public record MonthlyStickerResponseDto( @Schema(description = "날짜별 스티커 정보") Map> monthlyStickers -) {} +) { + public static MonthlyStickerResponseDto toDto(String childName, Map> monthlyStickers) { + return new MonthlyStickerResponseDto(childName, monthlyStickers); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/Diet/entity/Diet.java b/src/main/java/com/project/growfit/domain/Diet/entity/Diet.java index 37a610a..d504037 100644 --- a/src/main/java/com/project/growfit/domain/Diet/entity/Diet.java +++ b/src/main/java/com/project/growfit/domain/Diet/entity/Diet.java @@ -61,9 +61,9 @@ public class Diet extends BaseEntity { @Column(nullable = false) private double totalFat; - public static Diet create(String time, MealType mealType, DailyDiet dailyDiet, Child child, @NotBlank(message = "음식을 입력해주세요.") List foodList) { + public static Diet create(LocalTime time, MealType mealType, DailyDiet dailyDiet, Child child, @NotBlank(message = "음식을 입력해주세요.") List foodList) { Diet diet = new Diet(); - diet.time = LocalTime.parse(time); + diet.time = time; diet.mealType = mealType; diet.dailyDiet = dailyDiet; diet.child = child; diff --git a/src/main/java/com/project/growfit/domain/Diet/service/DietService.java b/src/main/java/com/project/growfit/domain/Diet/service/DietService.java index da61e49..e85bc0e 100644 --- a/src/main/java/com/project/growfit/domain/Diet/service/DietService.java +++ b/src/main/java/com/project/growfit/domain/Diet/service/DietService.java @@ -3,43 +3,29 @@ import com.project.growfit.domain.Diet.dto.request.AddDietRequestDto; import com.project.growfit.domain.Diet.dto.request.UpdateDietRequestDto; import com.project.growfit.domain.Diet.dto.request.UpdateFoodListRequestDto; +import com.project.growfit.domain.Diet.dto.response.*; import com.project.growfit.domain.Diet.entity.DietState; import com.project.growfit.domain.Diet.entity.Sticker; import com.project.growfit.global.response.ResultResponse; import org.springframework.web.multipart.MultipartFile; -public interface DietService { - - ResultResponse searchFoods(String keyword, int page, int size); +import java.util.List; - ResultResponse getFoodDetail(Long foodId); - - ResultResponse addDiet(AddDietRequestDto dto); +public interface DietService { + List searchFoods(String keyword, int page, int size); + FoodResponseDto getFoodDetail(Long foodId); + DietBasicDto addDiet(AddDietRequestDto dto); + DietBasicDto updateDiet(Long dietId, UpdateDietRequestDto dto); + DietBasicDto deleteDiet(Long dietId); + DietResponseDto getDietDetail(Long dietId); + DietBasicDto submitDiet(Long dietId, MultipartFile image, DietState dietState); + DietBasicDto deletePhoto(Long dietId); + DietBasicDto overrideDietNutrition(Long dietId, UpdateFoodListRequestDto dto); + MonthlyStickerResponseDto getMonthlyStickersByParent(String month); + DailyDietResponseDto getDailyDietByDate(String date); + DailyDietResponseDto markSticker(Long dailyDietId, Sticker sticker); + DailyDietResponseDto updateSticker(Long dailyDietId, Sticker sticker); + DailyDietResponseDto deleteSticker(Long dailyDietId); ResultResponse getDailyDietById(Long dailyId); - - ResultResponse getDailyDietByDate(String date); - - ResultResponse deleteDiet(Long dietId); - - ResultResponse updateDiet(Long dietId, UpdateDietRequestDto dto); - - ResultResponse overrideDietNutrition(Long dietId, UpdateFoodListRequestDto dto); - - ResultResponse markSticker(Long dailyDietId, Sticker sticker); - - ResultResponse getMonthlyStickersByParent(String month); - - ResultResponse uploadPhoto(Long dietId, MultipartFile image); - ResultResponse updateDietState(Long dietId, DietState dietState); - - ResultResponse updateDietTime(Long dietId, String newTime); - - ResultResponse getDietDetail(Long dietId); - - ResultResponse deletePhoto(Long dietId); - - ResultResponse deleteSticker(Long dailyDietId); - - ResultResponse updateSticker(Long dailyDietId, Sticker sticker); } diff --git a/src/main/java/com/project/growfit/domain/Diet/service/DietSetService.java b/src/main/java/com/project/growfit/domain/Diet/service/DietSetService.java index 9e6256f..30c327b 100644 --- a/src/main/java/com/project/growfit/domain/Diet/service/DietSetService.java +++ b/src/main/java/com/project/growfit/domain/Diet/service/DietSetService.java @@ -1,14 +1,16 @@ package com.project.growfit.domain.Diet.service; import com.project.growfit.domain.Diet.dto.request.SaveDietSetRequestDto; +import com.project.growfit.domain.Diet.dto.response.DietSetBasicDto; +import com.project.growfit.domain.Diet.dto.response.DietSetDetailResponseDto; +import com.project.growfit.domain.Diet.dto.response.DietSetResponseDto; import com.project.growfit.global.response.ResultResponse; +import org.springframework.data.domain.Page; public interface DietSetService { - ResultResponse saveDietSet(SaveDietSetRequestDto dto); - ResultResponse getAllDietSets(int page, int size); - ResultResponse getDietSetDetail(Long dietSetId); - - ResultResponse updateDietSet(Long dietSetId, SaveDietSetRequestDto dto); - - ResultResponse deleteDietSet(Long dietSetId); + DietSetBasicDto saveDietSet(SaveDietSetRequestDto dto); + DietSetBasicDto updateDietSet(Long dietSetId, SaveDietSetRequestDto dto); + DietSetBasicDto deleteDietSet(Long dietSetId); + Page getAllDietSets(int page, int size); + DietSetDetailResponseDto getDietSetDetail(Long dietSetId); } diff --git a/src/main/java/com/project/growfit/domain/Diet/service/impl/DietServiceImpl.java b/src/main/java/com/project/growfit/domain/Diet/service/impl/DietServiceImpl.java index d9bdca5..d686787 100644 --- a/src/main/java/com/project/growfit/domain/Diet/service/impl/DietServiceImpl.java +++ b/src/main/java/com/project/growfit/domain/Diet/service/impl/DietServiceImpl.java @@ -31,6 +31,7 @@ import java.time.LocalDate; import java.time.LocalTime; +import java.time.YearMonth; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; @@ -54,111 +55,107 @@ public class DietServiceImpl implements DietService { @Override @Transactional(readOnly = true) - public ResultResponse searchFoods(String keyword, int page, int size) { + public List searchFoods(String keyword, int page, int size) { authenticatedProvider.getAuthenticatedParent(); + if (keyword.trim().length() < 2) throw new BusinessException(ErrorCode.KEYWORD_TOO_SHORT); PageRequest pageRequest = PageRequest.of(page, size); Page foodPage = foodApiRepository.findByFoodNmContaining(keyword, pageRequest); - - List response = foodPage.stream() + return foodPage.stream() .map(FoodResponseDto::toDto) .toList(); - - return response.isEmpty() - ? ResultResponse.of(ResultCode.DIET_SEARCH_RESULT_EMPTY, response) - : ResultResponse.of(ResultCode.DIET_SEARCH_SUCCESS, response); } @Override @Transactional(readOnly = true) - public ResultResponse getFoodDetail(Long foodId) { + public FoodResponseDto getFoodDetail(Long foodId) { authenticatedProvider.getAuthenticatedParent(); FoodApi foodApi = findFoodOrThrow(foodId); - FoodResponseDto response = FoodResponseDto.toDto(foodApi); - return ResultResponse.of(ResultCode.DIET_DETAIL_RETRIEVAL_SUCCESS, response); + return FoodResponseDto.toDto(foodApi); } @Override @Transactional - public ResultResponse addDiet(AddDietRequestDto dto) { + public DietBasicDto addDiet(AddDietRequestDto dto) { Parent parent = authenticatedProvider.getAuthenticatedParent(); Child child = authenticatedProvider.getAuthenticatedChild(); - DailyDiet dailyDiet = getOrCreateDailyDiet(child, dto.date()); + + LocalDate date = parseDateOrThrow(dto.date()); + LocalTime time = parseTimeOrThrow(dto.eatTime()); + + if (dto.foodList() == null || dto.foodList().isEmpty()) throw new BusinessException(ErrorCode.EMPTY_FOOD_LIST); + List foodList = createFoodListFromDto(dto.foodList(), parent); + DailyDiet dailyDiet = getOrCreateDailyDiet(child, date); - Diet diet = Diet.create(dto.eatTime(), dto.mealType(), dailyDiet, child, foodList); + Diet diet = Diet.create(time, dto.mealType(), dailyDiet, child, foodList); dailyDiet.addDiet(diet); dailyDietRepository.save(dailyDiet); - return ResultResponse.of(ResultCode.DIET_ADD_SUCCESS, null); + return DietBasicDto.toDto(diet); } @Override @Transactional - public ResultResponse getDailyDietById(Long dailyId) { - Child child = authenticatedProvider.getAuthenticatedChild(); - DailyDiet dailyDiet = getDailyDietOrThrow(dailyId); - - if (!dailyDiet.getChild().equals(child)) { - throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); - } + public DietBasicDto updateDiet(Long dietId, UpdateDietRequestDto dto) { + Parent parent = authenticatedProvider.getAuthenticatedParent(); + Diet diet = getDietOrThrow(dietId); + DailyDiet dailyDiet = diet.getDailyDiet(); + List foodList = createFoodListFromDto(dto.foodList(), parent); - return ResultResponse.of( - ResultCode.DIET_RETRIEVAL_SUCCESS, - new DailyDietResponseDto( - dailyDiet.getId(), - dailyDiet.getDate().toString(), - dailyDiet.getTotalCalorie(), - groupDietsByMeal(dailyDiet.getDiets()) - ) - ); + applyNewFoodList(diet, dailyDiet, foodList, dto); + diet.updateTime(parseTimeOrThrow(dto.eatTime())); + return DietBasicDto.toDto(diet); } @Override - @Transactional(readOnly = true) - public ResultResponse getDailyDietByDate(String date) { - Child child = authenticatedProvider.getAuthenticatedChild(); - LocalDate parsedDate = LocalDate.parse(date); - DailyDiet dailyDiet = dailyDietRepository.findByChildAndDate(child, parsedDate) - .orElseThrow(() -> new BusinessException(ErrorCode.DAILY_DIET_NOT_FOUND)); + @Transactional + public DietBasicDto deleteDiet(Long dietId) { + authenticatedProvider.getAuthenticatedParent(); + Diet diet = getDietOrThrow(dietId); + DietBasicDto dto = DietBasicDto.toDto(diet); - return ResultResponse.of( - ResultCode.DAILY_DIET_RETRIEVAL_SUCCESS, - new DailyDietResponseDto( - dailyDiet.getId(), - dailyDiet.getDate().toString(), - dailyDiet.getTotalCalorie(), - groupDietsByMeal(dailyDiet.getDiets()) - ) - ); + dietRepository.delete(diet); + return dto; + } + + @Transactional(readOnly = true) + public DietResponseDto getDietDetail(Long dietId) { + authenticatedProvider.getAuthenticatedChild(); + Diet diet = getDietOrThrow(dietId); + return toDietResponseDto(diet); } @Override - @Transactional - public ResultResponse deleteDiet(Long dietId) { - authenticatedProvider.getAuthenticatedParent(); + public DietBasicDto submitDiet(Long dietId, MultipartFile image, DietState dietState) { + authenticatedProvider.getAuthenticatedChild(); Diet diet = getDietOrThrow(dietId); - dietRepository.delete(diet); - return ResultResponse.of(ResultCode.DIET_DELETE_SUCCESS, null); + if (image == null || image.isEmpty()) throw new BusinessException(ErrorCode.EMPTY_IMAGE_FILE); + String imageUrl = s3UploadService.saveFile(image, imageUploadPath); + diet.updateImage(imageUrl); + diet.updateState(dietState); + + return DietBasicDto.toDto(diet); } @Override @Transactional - public ResultResponse updateDiet(Long dietId, UpdateDietRequestDto dto) { - Parent parent = authenticatedProvider.getAuthenticatedParent(); + public DietBasicDto deletePhoto(Long dietId) { + authenticatedProvider.getAuthenticatedChild(); Diet diet = getDietOrThrow(dietId); - DailyDiet dailyDiet = diet.getDailyDiet(); - List foodList = createFoodListFromDto(dto.foodList(), parent); - applyNewFoodList(diet, dailyDiet, foodList, dto); - return ResultResponse.of(ResultCode.DIET_EDIT_SUCCESS, null); + if (diet.getImageUrl() == null) throw new BusinessException(ErrorCode.NO_IMAGE_TO_DELETE); + + s3UploadService.deleteFile(diet.getImageUrl()); + diet.updateImage(null); + return DietBasicDto.toDto(diet); } @Override @Transactional - public ResultResponse overrideDietNutrition(Long dietId, UpdateFoodListRequestDto dto) { + public DietBasicDto overrideDietNutrition(Long dietId, UpdateFoodListRequestDto dto) { Parent parent = authenticatedProvider.getAuthenticatedParent(); Diet diet = getDietOrThrow(dietId); DailyDiet dailyDiet = diet.getDailyDiet(); @@ -166,25 +163,16 @@ public ResultResponse overrideDietNutrition(Long dietId, UpdateFoodListReques applyNewFoodList(diet, dailyDiet, foodList); diet.updateState(DietState.MODIFIED); - return ResultResponse.of(ResultCode.DIET_OVERRIDE_SUCCESS, null); - } - - @Override - @Transactional - public ResultResponse markSticker(Long dailyDietId, Sticker sticker) { - authenticatedProvider.getAuthenticatedParent(); - DailyDiet dailyDiet = getDailyDietOrThrow(dailyDietId); - validateHasFood(dailyDiet); - dailyDiet.markSticker(sticker); - return ResultResponse.of(ResultCode.STICKER_MARK_SUCCESS, null); + return DietBasicDto.toDto(diet); } @Override @Transactional(readOnly = true) - public ResultResponse getMonthlyStickersByParent(String month) { + public MonthlyStickerResponseDto getMonthlyStickersByParent(String month) { Child child = authenticatedProvider.getAuthenticatedChild(); List diets = dailyDietRepository.findByChild(child); + parseMonthOrThrow(month); Map> monthlyStickers = new HashMap<>(); @@ -197,99 +185,100 @@ public ResultResponse getMonthlyStickersByParent(String month) { .put(diet.getDate().toString(), sticker); } } - return ResultResponse.of(ResultCode.CALENDAR_OVERVIEW_SUCCESS, new MonthlyStickerResponseDto(child.getName(), monthlyStickers)); + return MonthlyStickerResponseDto.toDto(child.getName(), monthlyStickers); } @Override - @Transactional - public ResultResponse uploadPhoto(Long dietId, MultipartFile image) { - authenticatedProvider.getAuthenticatedChild(); - Diet diet = getDietOrThrow(dietId); - String imageUrl = s3UploadService.saveFile(image, imageUploadPath); - diet.updateImage(imageUrl); - return ResultResponse.of(ResultCode.DIET_ADD_IMAGE_SUCCESS, null); - } - - @Override - @Transactional - public ResultResponse updateDietState(Long dietId, DietState dietState){ - authenticatedProvider.getAuthenticatedChild(); - Diet diet = getDietOrThrow(dietId); - diet.updateState(dietState); - return ResultResponse.of(ResultCode.CHILD_STATE_UPLOAD_SUCCESS, null); - } + @Transactional(readOnly = true) + public DailyDietResponseDto getDailyDietByDate(String date) { + Child child = authenticatedProvider.getAuthenticatedChild(); + LocalDate parsedDate = parseDateOrThrow(date); + DailyDiet dailyDiet = dailyDietRepository.findByChildAndDate(child, parsedDate) + .orElseThrow(() -> new BusinessException(ErrorCode.DAILY_DIET_NOT_FOUND)); - @Transactional - DailyDiet getOrCreateDailyDiet(Child child, String dtoDate) { - LocalDate date = LocalDate.parse(dtoDate); - return dailyDietRepository.findByChildAndDate(child, date) - .orElseGet(() -> { - DailyDiet newDailyDiet = new DailyDiet(child, date); - return dailyDietRepository.save(newDailyDiet); - }); + return new DailyDietResponseDto( + dailyDiet.getId(), + dailyDiet.getDate().toString(), + dailyDiet.getTotalCalorie(), + groupDietsByMeal(dailyDiet.getDiets() + )); } @Override @Transactional - public ResultResponse updateDietTime(Long dietId, String newTime) { + public DailyDietResponseDto markSticker(Long dailyDietId, Sticker sticker) { authenticatedProvider.getAuthenticatedParent(); - Diet diet = getDietOrThrow(dietId); - diet.updateTime(parseTimeOrThrow(newTime)); - return ResultResponse.of(ResultCode.DIET_EDIT_SUCCESS, null); - } - + DailyDiet dailyDiet = getDailyDietOrThrow(dailyDietId); + validateHasFood(dailyDiet); + dailyDiet.markSticker(sticker); - @Transactional(readOnly = true) - public ResultResponse getDietDetail(Long dietId) { - authenticatedProvider.getAuthenticatedChild(); - Diet diet = getDietOrThrow(dietId); - DietResponseDto response = toDietResponseDto(diet); - return ResultResponse.of(ResultCode.DIET_DETAIL_RETRIEVAL_SUCCESS, response); + return new DailyDietResponseDto( + dailyDiet.getId(), + dailyDiet.getDate().toString(), + dailyDiet.getTotalCalorie(), + groupDietsByMeal(dailyDiet.getDiets()) + ); } - @Override @Transactional - public ResultResponse deletePhoto(Long dietId) { - authenticatedProvider.getAuthenticatedChild(); - Diet diet = getDietOrThrow(dietId); - - if (diet.getImageUrl() != null) { - s3UploadService.deleteFile(diet.getImageUrl()); - diet.updateImage(null); - } + public DailyDietResponseDto updateSticker(Long dailyDietId, Sticker sticker) { + authenticatedProvider.getAuthenticatedParent(); + DailyDiet dailyDiet = getDailyDietOrThrow(dailyDietId); + extractedSticker(dailyDiet); + validateHasFood(dailyDiet); + dailyDiet.markSticker(sticker); - return ResultResponse.of(ResultCode.CHILD_PHOTO_DELETE_SUCCESS, null); + return new DailyDietResponseDto( + dailyDiet.getId(), + dailyDiet.getDate().toString(), + dailyDiet.getTotalCalorie(), + groupDietsByMeal(dailyDiet.getDiets()) + ); } - @Override @Transactional - public ResultResponse deleteSticker(Long dailyDietId) { + public DailyDietResponseDto deleteSticker(Long dailyDietId) { authenticatedProvider.getAuthenticatedParent(); DailyDiet dailyDiet = getDailyDietOrThrow(dailyDietId); extractedSticker(dailyDiet); dailyDiet.markSticker(null); - return ResultResponse.of(ResultCode.STICKER_DELETE_SUCCESS, null); + + return new DailyDietResponseDto( + dailyDiet.getId(), + dailyDiet.getDate().toString(), + dailyDiet.getTotalCalorie(), + groupDietsByMeal(dailyDiet.getDiets()) + ); } @Override @Transactional - public ResultResponse updateSticker(Long dailyDietId, Sticker sticker) { - DailyDiet dailyDiet = getDailyDietOrThrow(dailyDietId); - extractedSticker(dailyDiet); - validateHasFood(dailyDiet); - dailyDiet.markSticker(sticker); - return ResultResponse.of(ResultCode.STICKER_UPDATE_SUCCESS, null); - } - - private Diet getDietOrThrow(Long dietId) { - Child authenticatedChild = authenticatedProvider.getAuthenticatedChild(); - Diet diet = dietRepository.findDietWithFoodList(dietId) - .orElseThrow(() -> new BusinessException(ErrorCode.DIET_NOT_FOUND)); + public ResultResponse getDailyDietById(Long dailyId) { + Child child = authenticatedProvider.getAuthenticatedChild(); + DailyDiet dailyDiet = getDailyDietOrThrow(dailyId); - if (!diet.getChild().equals(authenticatedChild)) + if (!dailyDiet.getChild().equals(child)) { throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); + } - return diet; + return ResultResponse.of( + ResultCode.DIET_RETRIEVAL_SUCCESS, + new DailyDietResponseDto( + dailyDiet.getId(), + dailyDiet.getDate().toString(), + dailyDiet.getTotalCalorie(), + groupDietsByMeal(dailyDiet.getDiets()) + ) + ); + } + + @Transactional + DailyDiet getOrCreateDailyDiet(Child child, LocalDate date) { + return dailyDietRepository.findByChildAndDate(child, date) + .orElseGet(() -> { + DailyDiet newDailyDiet = new DailyDiet(child, date); + return dailyDietRepository.save(newDailyDiet); + }); } private DailyDiet getDailyDietOrThrow(Long dailyId) { @@ -304,16 +293,20 @@ private DailyDiet getDailyDietOrThrow(Long dailyId) { } private FoodApi findFoodOrThrow(Long foodId) { + if (foodId < 1) throw new BusinessException(ErrorCode.INVALID_FOOD_ID); return foodApiRepository.findById(foodId) - .orElseThrow(() -> new BusinessException(ErrorCode.DIET_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(ErrorCode.FOOD_NOT_FOUND)); } - private LocalTime parseTimeOrThrow(String timeStr) { - try { - return LocalTime.parse(timeStr); - } catch (DateTimeParseException e) { - throw new BusinessException(ErrorCode.INVALID_TIME_FORMAT); - } + private Diet getDietOrThrow(Long dietId) { + Child authenticatedChild = authenticatedProvider.getAuthenticatedChild(); + Diet diet = dietRepository.findDietWithFoodList(dietId) + .orElseThrow(() -> new BusinessException(ErrorCode.DIET_NOT_FOUND)); + + if (!diet.getChild().equals(authenticatedChild)) + throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); + + return diet; } private static void extractedSticker(DailyDiet diet) { @@ -330,7 +323,7 @@ private static void validateHasFood(DailyDiet dailyDiet) { } private List createFoodListFromDto(List foodItemDtos, Parent parent) { - if (foodItemDtos.isEmpty()) return new ArrayList<>(); + if (foodItemDtos == null || foodItemDtos.isEmpty()) throw new BusinessException(ErrorCode.EMPTY_FOOD_LIST); return foodItemDtos.stream() .map(foodItem -> { if (foodItem.foodId() != null) { @@ -391,4 +384,29 @@ private void applyNewFoodList(Diet diet, DailyDiet dailyDiet, List foodLis diet.edit(foodList); dailyDiet.recalculate(); } + + private static void parseMonthOrThrow(String month) { + YearMonth parsedMonth; + try { + parsedMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM")); + } catch (DateTimeParseException e) { + throw new BusinessException(ErrorCode.INVALID_MONTH_FORMAT); + } + } + + private LocalTime parseTimeOrThrow(String timeStr) { + try { + return LocalTime.parse(timeStr); + } catch (DateTimeParseException e) { + throw new BusinessException(ErrorCode.INVALID_DATETIME_FORMAT); + } + } + + private LocalDate parseDateOrThrow(String dateStr) { + try { + return LocalDate.parse(dateStr); + } catch (DateTimeParseException e) { + throw new BusinessException(ErrorCode.INVALID_DATETIME_FORMAT); + } + } } \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/Diet/service/impl/DietSetServiceImpl.java b/src/main/java/com/project/growfit/domain/Diet/service/impl/DietSetServiceImpl.java index e883111..ab477f1 100644 --- a/src/main/java/com/project/growfit/domain/Diet/service/impl/DietSetServiceImpl.java +++ b/src/main/java/com/project/growfit/domain/Diet/service/impl/DietSetServiceImpl.java @@ -1,6 +1,7 @@ package com.project.growfit.domain.Diet.service.impl; import com.project.growfit.domain.Diet.dto.request.SaveDietSetRequestDto; +import com.project.growfit.domain.Diet.dto.response.DietSetBasicDto; import com.project.growfit.domain.Diet.dto.response.DietSetDetailResponseDto; import com.project.growfit.domain.Diet.dto.response.DietSetResponseDto; import com.project.growfit.domain.Diet.dto.response.FoodResponseDto; @@ -38,7 +39,7 @@ public class DietSetServiceImpl implements DietSetService { @Override @Transactional - public ResultResponse saveDietSet(SaveDietSetRequestDto dto) { + public DietSetBasicDto saveDietSet(SaveDietSetRequestDto dto) { Parent parent = authenticatedProvider.getAuthenticatedParent(); List foods = dto.foodList().stream() @@ -48,30 +49,61 @@ public ResultResponse saveDietSet(SaveDietSetRequestDto dto) { DietSet dietSet = DietSet.create(dto, parent, foods); dietSetRepository.save(dietSet); - return ResultResponse.of(ResultCode.DIET_SET_SAVE_SUCCESS, null); + return DietSetBasicDto.toDto(dietSet); } + @Override + @Transactional + public DietSetBasicDto updateDietSet(Long dietSetId, SaveDietSetRequestDto dto) { + Parent parent = authenticatedProvider.getAuthenticatedParent(); + DietSet dietSet = getDietSetOrThrow(dietSetId); + + dietSet.clearFoods(); + + List updatedFoods = dto.foodList().stream() + .map(foodItem -> DietSetFood.create(foodItem, foodApiRepository, customFoodRepository, parent)) + .toList(); + + dietSet.update(dto.setName(), updatedFoods); + + return DietSetBasicDto.toDto(dietSet); + } + + @Override + @Transactional + public DietSetBasicDto deleteDietSet(Long dietSetId) { + DietSet dietSet = getDietSetOrThrow(dietSetId); + dietSetRepository.delete(dietSet); + return DietSetBasicDto.toDto(dietSet); + } + + private DietSet getDietSetOrThrow(Long dietSetId) { + Parent parent = authenticatedProvider.getAuthenticatedParent(); + DietSet dietSet = dietSetRepository.findById(dietSetId) + .orElseThrow(() -> new BusinessException(ErrorCode.DIET_SET_NOT_FOUND)); + if (!dietSet.getParent().equals(parent)) + throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); + return dietSet; + } @Override @Transactional(readOnly = true) - public ResultResponse getAllDietSets(int page, int size) { + public Page getAllDietSets(int page, int size) { Parent parent = authenticatedProvider.getAuthenticatedParent(); Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); Page sets = dietSetRepository.findByParent(parent, pageable); - Page response = sets.map(set -> new DietSetResponseDto( + return sets.map(set -> new DietSetResponseDto( set.getId(), set.getSetName(), set.getTotalCalorie(), set.getFoods().stream().map(DietSetFood::getName).toList() )); - - return ResultResponse.of(ResultCode.DIET_SET_LIST_SUCCESS, response); } @Override @Transactional(readOnly = true) - public ResultResponse getDietSetDetail(Long dietSetId) { + public DietSetDetailResponseDto getDietSetDetail(Long dietSetId) { authenticatedProvider.getAuthenticatedParent(); DietSet set = getDietSetOrThrow(dietSetId); @@ -86,42 +118,6 @@ public ResultResponse getDietSetDetail(Long dietSetId) { ) ).toList(); - DietSetDetailResponseDto response = new DietSetDetailResponseDto(set.getSetName(), foodDetails); - return ResultResponse.of(ResultCode.DIET_SET_RETRIEVAL_SUCCESS, response); - } - - @Override - @Transactional - public ResultResponse updateDietSet(Long dietSetId, SaveDietSetRequestDto dto) { - Parent parent = authenticatedProvider.getAuthenticatedParent(); - DietSet set = getDietSetOrThrow(dietSetId); - - set.clearFoods(); - - List updatedFoods = dto.foodList().stream() - .map(foodItem -> DietSetFood.create(foodItem, foodApiRepository, customFoodRepository, parent)) - .toList(); - - set.update(dto.setName(), updatedFoods); - - return ResultResponse.of(ResultCode.DIET_SET_EDIT_SUCCESS, null); - } - - @Override - @Transactional - public ResultResponse deleteDietSet(Long dietSetId) { - DietSet set = getDietSetOrThrow(dietSetId); - dietSetRepository.delete(set); - return ResultResponse.of(ResultCode.DIET_SET_DELETE_SUCCESS, null); - } - - private DietSet getDietSetOrThrow(Long dietSetId) { - Parent parent = authenticatedProvider.getAuthenticatedParent(); - DietSet set = dietSetRepository.findById(dietSetId) - .orElseThrow(() -> new BusinessException(ErrorCode.DIET_SET_NOT_FOUND)); - if (!set.getParent().equals(parent)) - throw new BusinessException(ErrorCode.FORBIDDEN_ACCESS); - - return set; + return new DietSetDetailResponseDto(set.getSetName(), foodDetails); } } diff --git a/src/main/java/com/project/growfit/domain/User/controller/AuthChildController.java b/src/main/java/com/project/growfit/domain/User/controller/AuthChildController.java index 0132e97..ad70de4 100644 --- a/src/main/java/com/project/growfit/domain/User/controller/AuthChildController.java +++ b/src/main/java/com/project/growfit/domain/User/controller/AuthChildController.java @@ -2,69 +2,76 @@ import com.project.growfit.domain.User.dto.request.AuthChildRequestDto; import com.project.growfit.domain.User.dto.request.FindChildPasswordRequestDto; +import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto; +import com.project.growfit.domain.User.dto.response.ChildResponseDto; import com.project.growfit.domain.User.service.AuthChildService; +import com.project.growfit.global.response.ResultCode; import com.project.growfit.global.response.ResultResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +@Validated @RestController @RequestMapping("/api/child") @RequiredArgsConstructor -@Tag(name = "Child Auth API", description = "아이 회원가입/로그인 관련 API") +@Tag(name = "아이 로그인 및 회원가입 API", description = "아이 회원가입/로그인 관련 API입니다.") public class AuthChildController { private final AuthChildService authChildService; @Operation(summary = "아이 회원가입 시 인증 코드로 정보 조회") @GetMapping("/register/code") - public ResponseEntity registerChildByCode(@RequestParam String code) { - ResultResponse resultResponse = authChildService.findByCode(code); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + public ResultResponse registerChildByCode(@NotBlank @RequestParam String code) { + ChildResponseDto dto = authChildService.findByCode(code); + + return ResultResponse.of(ResultCode.INFO_SUCCESS, dto); } @Operation(summary = "아이 회원가입 시 아이디 & 비밀번호 & 닉네임 등록") @PostMapping("/register/{child_id}/credentials") - public ResponseEntity registerChildCredentials(@PathVariable Long child_id, - @RequestBody AuthChildRequestDto request) { - ResultResponse resultResponse = authChildService.registerChildCredentials(child_id, request); + public ResultResponse registerChildCredentials(@PathVariable Long child_id, + @Valid @RequestBody AuthChildRequestDto request) { + ChildInfoResponseDto dto = authChildService.registerChildCredentials(child_id, request); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.INFO_REGISTRATION_SUCCESS, dto); } @Operation(summary = "아이 로그인") @PostMapping("/login") - public ResponseEntity loginChild(@RequestBody AuthChildRequestDto request, HttpServletResponse response) { - ResultResponse resultResponse = authChildService.login(request, response); + public ResultResponse loginChild(@Valid @RequestBody AuthChildRequestDto request, HttpServletResponse response) { + ChildResponseDto dto = authChildService.login(request, response); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.LOGIN_SUCCESS, dto); } @Operation(summary = "아이 로그아웃") @PostMapping("/logout") - public ResponseEntity logoutChild(HttpServletResponse response) { - ResultResponse resultResponse = authChildService.logout(response); + public ResultResponse logoutChild(HttpServletResponse response) { + ChildResponseDto dto = authChildService.logout(response); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.LOGOUT_SUCCESS, dto); } @Operation(summary = "아이 인증코드로 ID 찾기") @GetMapping("/find/id") - public ResponseEntity findChildIdByCode(@RequestParam String code) { - ResultResponse resultResponse = authChildService.findChildID(code); + public ResultResponse findChildIdByCode( + @RequestParam @NotBlank(message = "인증 코드는 필수입니다.") String code){ + ChildResponseDto dto = authChildService.findChildID(code); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.INFO_SUCCESS, dto); } @Operation(summary = "아이 비밀번호 재설정") @PostMapping("/find/password") - public ResponseEntity resetChildPassword(@RequestBody FindChildPasswordRequestDto request) { - ResultResponse resultResponse = authChildService.findChildPassword(request); + public ResultResponse resetChildPassword(@Valid @RequestBody FindChildPasswordRequestDto request) { + ChildInfoResponseDto dto = authChildService.findChildPassword(request); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.INFO_REGISTRATION_SUCCESS, dto); } } diff --git a/src/main/java/com/project/growfit/domain/User/controller/AuthParentController.java b/src/main/java/com/project/growfit/domain/User/controller/AuthParentController.java index b139e86..571e0e9 100644 --- a/src/main/java/com/project/growfit/domain/User/controller/AuthParentController.java +++ b/src/main/java/com/project/growfit/domain/User/controller/AuthParentController.java @@ -2,40 +2,43 @@ import com.google.zxing.WriterException; import com.project.growfit.domain.User.dto.request.AuthParentRequestDto; +import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto; +import com.project.growfit.domain.User.dto.response.ChildQrCodeResponseDto; import com.project.growfit.domain.User.service.AuthParentService; import com.project.growfit.global.auth.dto.CustomUserDetails; +import com.project.growfit.global.response.ResultCode; import com.project.growfit.global.response.ResultResponse; 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.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +@Validated @RestController @RequestMapping("/api/parent") @RequiredArgsConstructor -@Tag(name = "Parent Auth API", description = "부모 회원 관련 API") +@Tag(name = "부모 로그인 및 회원가입 API", description = "부모 회원가입/로그인 관련 API입니다.") public class AuthParentController { private final AuthParentService parentService; @Operation(summary = "부모 회원가입 시 아이 등록") @PostMapping("/child") - public ResponseEntity registerChild(@AuthenticationPrincipal CustomUserDetails user, - @RequestBody AuthParentRequestDto request){ - ResultResponse resultResponse = parentService.registerChild(user, request); + public ResultResponse registerChild(@Valid @RequestBody AuthParentRequestDto request){ + ChildInfoResponseDto dto = parentService.registerChild(request); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.SIGNUP_SUCCESS, dto); } @Operation(summary = "아이 QR 코드 생성") @GetMapping("/child/qr") - public ResponseEntity createQrCode(@AuthenticationPrincipal CustomUserDetails user) throws WriterException { - ResultResponse resultResponse = parentService.createQR(user); + public ResultResponse createQrCode(@AuthenticationPrincipal CustomUserDetails user) throws WriterException { + ChildQrCodeResponseDto dto = parentService.createQR(); - return ResponseEntity.status(HttpStatus.OK).body(resultResponse); + return ResultResponse.of(ResultCode.QR_GENERATION_SUCCESS, dto); } } diff --git a/src/main/java/com/project/growfit/domain/User/controller/OAuthController.java b/src/main/java/com/project/growfit/domain/User/controller/OAuthController.java index 76dd8e5..27935ad 100644 --- a/src/main/java/com/project/growfit/domain/User/controller/OAuthController.java +++ b/src/main/java/com/project/growfit/domain/User/controller/OAuthController.java @@ -10,13 +10,12 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/oauth") @RequiredArgsConstructor -@Tag(name = "Social OAuth API", description = "소셜 로그인 관련 API (카카오)") +@Tag(name = "부모 소셜 로그인 API", description = "부모 소셜 로그인(카카오) 관련 API입니다.") public class OAuthController { private final OauthService oauthService; @@ -60,7 +59,8 @@ public void kakaoLogin(@RequestParam(value = "code", required = false) String co @PostMapping("/logout") public ResultResponse kakaoLogout(@RequestParam(value = "code", required = false) String code, HttpServletResponse response) { - return oauthService.kakaoLogout(code, response); + String message = oauthService.kakaoLogout(code, response); + return ResultResponse.of(ResultCode.LOGOUT_SUCCESS, message); } } diff --git a/src/main/java/com/project/growfit/domain/User/dto/request/AuthChildRequestDto.java b/src/main/java/com/project/growfit/domain/User/dto/request/AuthChildRequestDto.java index 3abc5b8..5f6f156 100644 --- a/src/main/java/com/project/growfit/domain/User/dto/request/AuthChildRequestDto.java +++ b/src/main/java/com/project/growfit/domain/User/dto/request/AuthChildRequestDto.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; public record AuthChildRequestDto( @@ -11,6 +12,8 @@ public record AuthChildRequestDto( @Schema(description = "아이 로그인 비밀번호", example = "password123") @NotBlank(message = "비밀번호를 입력해주세요.") + @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*?&]{8,}$", + message = "비밀번호는 8자 이상, 영문자와 숫자를 포함해야 합니다.") String childPassword, @Schema(description = "닉네임 입력", example = "민준콩") diff --git a/src/main/java/com/project/growfit/domain/User/dto/request/FindChildPasswordRequestDto.java b/src/main/java/com/project/growfit/domain/User/dto/request/FindChildPasswordRequestDto.java index d76c156..d7e47ff 100644 --- a/src/main/java/com/project/growfit/domain/User/dto/request/FindChildPasswordRequestDto.java +++ b/src/main/java/com/project/growfit/domain/User/dto/request/FindChildPasswordRequestDto.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; public record FindChildPasswordRequestDto( @@ -15,6 +16,8 @@ public record FindChildPasswordRequestDto( @Schema(description = "새로운 비밀번호", example = "newPassword123") @NotBlank(message = "새로운 비밀번호를 입력해주세요.") + @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*?&]{8,}$", + message = "비밀번호는 8자 이상, 영문자와 숫자를 포함해야 합니다.") String new_password ) { } diff --git a/src/main/java/com/project/growfit/domain/User/dto/response/ChildBodyInfoResponseDto.java b/src/main/java/com/project/growfit/domain/User/dto/response/ChildBodyInfoResponseDto.java index 8611fff..13cea88 100644 --- a/src/main/java/com/project/growfit/domain/User/dto/response/ChildBodyInfoResponseDto.java +++ b/src/main/java/com/project/growfit/domain/User/dto/response/ChildBodyInfoResponseDto.java @@ -1,6 +1,7 @@ package com.project.growfit.domain.User.dto.response; import com.project.growfit.domain.User.entity.ChildBodyInfo; + import java.time.LocalDateTime; public record ChildBodyInfoResponseDto( diff --git a/src/main/java/com/project/growfit/domain/User/dto/response/ChildIdResponse.java b/src/main/java/com/project/growfit/domain/User/dto/response/ChildIdResponse.java deleted file mode 100644 index 2a28581..0000000 --- a/src/main/java/com/project/growfit/domain/User/dto/response/ChildIdResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.project.growfit.domain.User.dto.response; - -public record ChildIdResponse( - Long child_id -) {} \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/User/dto/response/ChildInfoResponseDto.java b/src/main/java/com/project/growfit/domain/User/dto/response/ChildInfoResponseDto.java index 7408aa2..f6379b6 100644 --- a/src/main/java/com/project/growfit/domain/User/dto/response/ChildInfoResponseDto.java +++ b/src/main/java/com/project/growfit/domain/User/dto/response/ChildInfoResponseDto.java @@ -1,7 +1,6 @@ package com.project.growfit.domain.User.dto.response; import com.project.growfit.domain.User.entity.Child; -import com.project.growfit.domain.User.entity.ChildBodyInfo; import com.project.growfit.domain.User.entity.ChildGender; public record ChildInfoResponseDto( diff --git a/src/main/java/com/project/growfit/domain/User/dto/response/ChildResponseDto.java b/src/main/java/com/project/growfit/domain/User/dto/response/ChildResponseDto.java new file mode 100644 index 0000000..9e3b06e --- /dev/null +++ b/src/main/java/com/project/growfit/domain/User/dto/response/ChildResponseDto.java @@ -0,0 +1,25 @@ +package com.project.growfit.domain.User.dto.response; + +import com.project.growfit.domain.User.entity.Child; + +public record ChildResponseDto( + Long child_id, + String child_name, + String child_login_id +) { + public static ChildResponseDto toDto(Child child, String login_id) { + return new ChildResponseDto( + child.getId(), + child.getName(), + login_id + ); + } + + public static ChildResponseDto toDto(Child child) { + return new ChildResponseDto( + child.getId(), + child.getName(), + child.getLoginId() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/User/dto/response/ParentResponse.java b/src/main/java/com/project/growfit/domain/User/dto/response/ParentResponseDto.java similarity index 85% rename from src/main/java/com/project/growfit/domain/User/dto/response/ParentResponse.java rename to src/main/java/com/project/growfit/domain/User/dto/response/ParentResponseDto.java index a0d51a8..b32f02d 100644 --- a/src/main/java/com/project/growfit/domain/User/dto/response/ParentResponse.java +++ b/src/main/java/com/project/growfit/domain/User/dto/response/ParentResponseDto.java @@ -3,14 +3,14 @@ import com.project.growfit.domain.User.entity.Parent; -public record ParentResponse( +public record ParentResponseDto( String email, String name, String kakaoIdentifier, String profileImage, String roles ) { - public ParentResponse(Parent parent) { + public ParentResponseDto(Parent parent) { this( parent.getEmail(), parent.getNickname(), diff --git a/src/main/java/com/project/growfit/domain/User/repository/ChildRepository.java b/src/main/java/com/project/growfit/domain/User/repository/ChildRepository.java index 9c757b2..98a5fb1 100644 --- a/src/main/java/com/project/growfit/domain/User/repository/ChildRepository.java +++ b/src/main/java/com/project/growfit/domain/User/repository/ChildRepository.java @@ -18,5 +18,4 @@ public interface ChildRepository extends JpaRepository { boolean existsByCodeNumberAndLoginId(String code, String login_id); boolean existsByCodeNumber(String code); - boolean existsByLoginIdOrPassword(String login_id, String password); } \ No newline at end of file diff --git a/src/main/java/com/project/growfit/domain/User/service/AuthChildService.java b/src/main/java/com/project/growfit/domain/User/service/AuthChildService.java index c613124..19cafd1 100644 --- a/src/main/java/com/project/growfit/domain/User/service/AuthChildService.java +++ b/src/main/java/com/project/growfit/domain/User/service/AuthChildService.java @@ -2,14 +2,15 @@ import com.project.growfit.domain.User.dto.request.AuthChildRequestDto; import com.project.growfit.domain.User.dto.request.FindChildPasswordRequestDto; -import com.project.growfit.global.response.ResultResponse; +import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto; +import com.project.growfit.domain.User.dto.response.ChildResponseDto; import jakarta.servlet.http.HttpServletResponse; public interface AuthChildService { - ResultResponse findByCode(String code); - ResultResponse registerChildCredentials(Long child_id, AuthChildRequestDto request); - ResultResponse login(AuthChildRequestDto request, HttpServletResponse response); - ResultResponse logout(HttpServletResponse response); - ResultResponse findChildID(String code); - ResultResponse findChildPassword(FindChildPasswordRequestDto request); + ChildResponseDto findByCode(String code); + ChildInfoResponseDto registerChildCredentials(Long child_id, AuthChildRequestDto request); + ChildResponseDto login(AuthChildRequestDto request, HttpServletResponse response); + ChildResponseDto logout(HttpServletResponse response); + ChildResponseDto findChildID(String code); + ChildInfoResponseDto findChildPassword(FindChildPasswordRequestDto request); } diff --git a/src/main/java/com/project/growfit/domain/User/service/AuthParentService.java b/src/main/java/com/project/growfit/domain/User/service/AuthParentService.java index 7a12d8e..eb7671e 100644 --- a/src/main/java/com/project/growfit/domain/User/service/AuthParentService.java +++ b/src/main/java/com/project/growfit/domain/User/service/AuthParentService.java @@ -2,10 +2,10 @@ import com.google.zxing.WriterException; import com.project.growfit.domain.User.dto.request.AuthParentRequestDto; -import com.project.growfit.global.auth.dto.CustomUserDetails; -import com.project.growfit.global.response.ResultResponse; +import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto; +import com.project.growfit.domain.User.dto.response.ChildQrCodeResponseDto; public interface AuthParentService { - ResultResponse registerChild(CustomUserDetails user, AuthParentRequestDto request); - ResultResponse createQR(CustomUserDetails user) throws WriterException; + ChildInfoResponseDto registerChild(AuthParentRequestDto request); + ChildQrCodeResponseDto createQR() throws WriterException; } diff --git a/src/main/java/com/project/growfit/domain/User/service/OauthService.java b/src/main/java/com/project/growfit/domain/User/service/OauthService.java index a403a4a..4c29ee1 100644 --- a/src/main/java/com/project/growfit/domain/User/service/OauthService.java +++ b/src/main/java/com/project/growfit/domain/User/service/OauthService.java @@ -1,7 +1,7 @@ package com.project.growfit.domain.User.service; import com.project.growfit.domain.User.dto.request.ParentOAuthRequestDto; -import com.project.growfit.domain.User.dto.response.ParentResponse; +import com.project.growfit.domain.User.dto.response.ParentResponseDto; import com.project.growfit.global.response.ResultResponse; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Service; @@ -13,8 +13,8 @@ public interface OauthService { String getKakaoAccessToken(String code); HashMap getUserKakaoInfo(String access_token); ResultResponse kakaoLogin(String access_token, HttpServletResponse response); - ResultResponse kakaoLogout(String access_token, HttpServletResponse response); - ParentResponse findByUserKakaoIdentifier(String kakaoIdentifier); + String kakaoLogout(String access_token, HttpServletResponse response); + ParentResponseDto findByUserKakaoIdentifier(String kakaoIdentifier); Long signUp(ParentOAuthRequestDto requestDto); diff --git a/src/main/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImpl.java b/src/main/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImpl.java index 770ff00..068d033 100644 --- a/src/main/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImpl.java +++ b/src/main/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImpl.java @@ -2,8 +2,8 @@ import com.project.growfit.domain.User.dto.request.AuthChildRequestDto; import com.project.growfit.domain.User.dto.request.FindChildPasswordRequestDto; -import com.project.growfit.domain.User.dto.response.ChildIdResponse; import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto; +import com.project.growfit.domain.User.dto.response.ChildResponseDto; import com.project.growfit.domain.User.entity.Child; import com.project.growfit.domain.User.repository.ChildRepository; import com.project.growfit.domain.User.service.AuthChildService; @@ -15,8 +15,6 @@ import com.project.growfit.global.exception.ErrorCode; import com.project.growfit.global.redis.entity.TokenRedis; import com.project.growfit.global.redis.repository.TokenRedisRepository; -import com.project.growfit.global.response.ResultCode; -import com.project.growfit.global.response.ResultResponse; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -29,7 +27,6 @@ @Slf4j @Service -@Transactional @RequiredArgsConstructor public class AuthChildServiceImpl implements AuthChildService { private final ChildRepository childRepository; @@ -40,42 +37,38 @@ public class AuthChildServiceImpl implements AuthChildService { private final CustomAuthenticationProvider authenticationProvider; private final AuthenticatedUserProvider authenticatedUser; - public ResultResponse findByCode(String code) { + public ChildResponseDto findByCode(String code) { log.info("[findByCode] 코드로 아이 정보 조회 요청: {}", code); Child child = getChild(code); - Long childPid = child.getId(); - - log.info("[findByCode] 아이 정보 PID 조회 성공: {}", childPid); - return new ResultResponse<>(ResultCode.INFO_SUCCESS, new ChildIdResponse(childPid)); + log.info("[findByCode] 아이 정보 PID 조회 성공: {}", child.getId()); + return ChildResponseDto.toDto(child); } @Override - public ResultResponse registerChildCredentials(Long child_id, AuthChildRequestDto request) { + @Transactional + public ChildInfoResponseDto registerChildCredentials(Long child_id, AuthChildRequestDto request) { log.debug("[registerChildCredentials] 아이 계정 정보 등록 요청: child_id={}, child_login_id={}", child_id, request.childId()); - boolean isExists = childRepository.existsByLoginIdOrPassword(request.childId(), request.childPassword()); - - if (isExists) { + if (childRepository.existsByLoginId(request.childId())) { throw new BusinessException(ErrorCode.CHILD_ALREADY_EXISTS); } - else { - Child child = getChild(child_id); - child.updateCredentials(request.childId(), passwordEncoder.encode(request.childPassword()), request.nickname()); - childRepository.save(child); - } + validatePasswordStrength(request.childPassword()); + Child child = getChild(child_id); + + child.updateCredentials(request.childId(), passwordEncoder.encode(request.childPassword()), request.nickname()); + childRepository.save(child); log.info("[registerChildCredentials] 아이 계정 정보 등록 완료: child_id={}", child_id); - return new ResultResponse<>(ResultCode.INFO_REGISTRATION_SUCCESS, null); + + return ChildInfoResponseDto.toDto(child); } @Override - public ResultResponse login(AuthChildRequestDto request, HttpServletResponse response) { + public ChildResponseDto login(AuthChildRequestDto request, HttpServletResponse response) { log.info("[login] 아이 로그인 시도 . . . : child_login_id={}", request.childId()); Authentication authenticate; try { - authenticate = authenticationProvider.authenticate( - new UsernamePasswordAuthenticationToken(request.childId(), request.childPassword()) - ); + authenticate = authenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(request.childId(), request.childPassword())); SecurityContextHolder.getContext().setAuthentication(authenticate); } catch (Exception e) { log.warn("[login] 로그인 실패: child_login_id={}", request.childId()); @@ -97,11 +90,12 @@ public ResultResponse login(AuthChildRequestDto request, HttpServletResponse cookieService.saveAccessTokenToCookie(response, newAccessToken); log.debug("[login] AccessToken을 쿠키에 저장 완료: child_login_id={}", request.childId()); - return new ResultResponse<>(ResultCode.LOGIN_SUCCESS, null); + return ChildResponseDto.toDto(child); } - public ResultResponse logout(HttpServletResponse response) { - String loginId = authenticatedUser.getAuthenticatedChild().getLoginId(); + public ChildResponseDto logout(HttpServletResponse response) { + Child child = authenticatedUser.getAuthenticatedChild(); + String loginId = child.getLoginId(); log.info("[logout] 아이 로그아웃 요청: loginId={}", loginId); tokenRedisRepository.deleteById(loginId); @@ -110,20 +104,19 @@ public ResultResponse logout(HttpServletResponse response) { cookieService.clearCookie(response, "accessToken"); log.debug("[logout] accessToken 쿠키 만료 처리 완료: loginId={}", loginId); - return new ResultResponse<>(ResultCode.LOGOUT_SUCCESS, null); + return ChildResponseDto.toDto(child, child.getLoginId()); } - - public ResultResponse findChildID(String code) { - log.info("[findChildID] 코드로 아이 ID 찾기 요청: {}", code); + @Transactional + public ChildResponseDto findChildID(String code) { + log.info("[findChildID] 인증 코드 수신: {}", code); Child child = getChild(code); - ChildInfoResponseDto dto = ChildInfoResponseDto.toDto(child); + log.info("[findChildID] 아이 ID 찾기 성공: {}", child.getId()); - log.info("[findChildID] 아이 ID 찾기 성공: {}", dto); - return new ResultResponse<>(ResultCode.INFO_SUCCESS, dto); + return ChildResponseDto.toDto(child, child.getLoginId()); } - @Override - public ResultResponse findChildPassword(FindChildPasswordRequestDto request) { + @Transactional + public ChildInfoResponseDto findChildPassword(FindChildPasswordRequestDto request) { log.info("[findChildPassword] 아이 비밀번호 찾기 요청: user_id={}, code={}", request.user_id(), request.code()); boolean isExist = childRepository.existsByCodeNumberAndLoginId(request.code(), request.user_id()); if (!isExist) { @@ -131,12 +124,14 @@ public ResultResponse findChildPassword(FindChildPasswordRequestDto request) throw new BusinessException(ErrorCode.CHILD_NOT_FOUND); } Child child = getChild(request.code()); + validatePasswordStrength(request.new_password()); child.updatePassword(passwordEncoder.encode(request.new_password())); childRepository.save(child); log.info("[findChildPassword] 비밀번호 변경 완료: user_id={}", request.user_id()); - return new ResultResponse<>(ResultCode.INFO_REGISTRATION_SUCCESS, null); + + return ChildInfoResponseDto.toDto(child); } private Child getChild(Long child_id) { @@ -149,10 +144,15 @@ private Child getChildByChildId(String login_id) { .orElseThrow(() -> new BusinessException(ErrorCode.CHILD_NOT_FOUND)); } - private Child getChild(String code) { return childRepository.findByCodeNumber(code) .orElseThrow(() -> new BusinessException(ErrorCode.CHILD_NOT_FOUND)); } + private void validatePasswordStrength(String password) { + String regex = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*?&]{8,}$"; + if (!password.matches(regex)) { + throw new BusinessException(ErrorCode.WEAK_PASSWORD); + } + } } diff --git a/src/main/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImpl.java b/src/main/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImpl.java index 748f5b1..7db318d 100644 --- a/src/main/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImpl.java +++ b/src/main/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImpl.java @@ -12,16 +12,13 @@ import com.project.growfit.domain.User.entity.Parent; import com.project.growfit.domain.User.entity.ROLE; import com.project.growfit.domain.User.repository.ChildRepository; -import com.project.growfit.domain.User.repository.ParentRepository; import com.project.growfit.domain.User.service.AuthParentService; -import com.project.growfit.global.auth.dto.CustomUserDetails; import com.project.growfit.global.auth.service.AuthenticatedUserProvider; import com.project.growfit.global.exception.BusinessException; import com.project.growfit.global.exception.ErrorCode; -import com.project.growfit.global.response.ResultCode; -import com.project.growfit.global.response.ResultResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,21 +28,22 @@ @Slf4j @Service -@Transactional @RequiredArgsConstructor public class AuthParentServiceImpl implements AuthParentService { - private final ParentRepository parentRepository; + + @Value("${app.frontend.url}") + private String frontendUrl; private final ChildRepository childRepository; private final AuthenticatedUserProvider authenticatedProvider; @Override - public ResultResponse registerChild(CustomUserDetails user, AuthParentRequestDto request) { - log.info("[registerChild] 자녀 등록 요청: user_id={}, child_name={}", user.getUserId(), request.child_name()); - Parent parent = getParent(user); - Child child = createChild(request); - boolean childExists = parent.hasChildWithName(child.getName()); + @Transactional + public ChildInfoResponseDto registerChild(AuthParentRequestDto request) { + Parent parent = authenticatedProvider.getAuthenticatedParent(); + log.info("[registerChild] 자녀 등록 요청: parent_id={}, child_name={}", parent.getId(), request.child_name()); - if (childExists) { + Child child = createChild(request); + if (parent.hasChildWithName(child.getName())) { log.warn("[registerChild] 중복된 자녀 등록 시도: parent_id={}, child_name={}", parent.getId(), request.child_name()); throw new BusinessException(ErrorCode.CHILD_ALREADY_EXISTS); } @@ -53,14 +51,16 @@ public ResultResponse registerChild(CustomUserDetails user, AuthParentRequest updateNickname(request, parent); parent.addChild(child); childRepository.save(child); - ChildInfoResponseDto dto = ChildInfoResponseDto.toDto(child); - log.info("[registerChild] 자녀 등록 완료: child_id={}, parent_id={}", child.getId(), parent.getId()); - return new ResultResponse<>(ResultCode.SIGNUP_SUCCESS, dto); + + return ChildInfoResponseDto.toDto(child); } @Override - public ResultResponse createQR(CustomUserDetails user) throws WriterException { + @Transactional + public ChildQrCodeResponseDto createQR() throws WriterException { + Parent parent = authenticatedProvider.getAuthenticatedParent(); + int width = 200; int height = 200; String uniqueCode = UUID.randomUUID().toString(); @@ -68,8 +68,8 @@ public ResultResponse createQR(CustomUserDetails user) throws WriterException Child child = authenticatedProvider.getAuthenticatedChild(); Long id = child.getId(); - log.info("[createQR] QR 코드 생성 요청: user_id={}", user.getUserId()); - String url = "http://localhost:8080/api/child/register/" + id + "/credentials"; + log.info("[createQR] QR 코드 생성 요청: user_id={}", parent.getId()); + String url = frontendUrl + "/api/child/register/" + id + "/credentials"; if(child.getCodeNumber() != null){ throw new BusinessException(ErrorCode.QR_ALREADY_EXISTS); @@ -83,9 +83,8 @@ public ResultResponse createQR(CustomUserDetails user) throws WriterException MatrixToImageWriter.writeToStream(encode, "PNG", out); String base64QrCode = Base64.getEncoder().encodeToString(out.toByteArray()); log.info("[createQR] QR 코드 생성 완료: child_id={}, qr_code={}", id, base64QrCode); - ChildQrCodeResponseDto dto = ChildQrCodeResponseDto.toDto(child, base64QrCode, uniqueCode); - return new ResultResponse<>(ResultCode.QR_GENERATION_SUCCESS, dto); + return ChildQrCodeResponseDto.toDto(child, base64QrCode, uniqueCode); }catch (Exception e){ log.warn("[createQR]QR Code OutputStream 도중 Exception 발생: {}", e.getMessage()); throw new BusinessException(ErrorCode.QR_GENERATION_FAILED); @@ -107,24 +106,6 @@ protected Child createChild(AuthParentRequestDto request){ null, ROLE.fromString("CHILD")); } - - @Transactional - protected Parent getParent(CustomUserDetails user) { - return parentRepository.findByEmail(user.getUserId()) - .orElseThrow(() -> { - log.warn("[registerChild] 부모 정보 조회 실패: 존재하지 않는 사용자 user_id={}", user.getUserId()); - return new BusinessException(ErrorCode.USER_NOT_FOUND); - }); - } - @Transactional - protected Child getChild(Long child_id) { - return childRepository.findById(child_id) - .orElseThrow(() -> { - log.warn("[createQR] 아이 정보 조회 실패: 존재하지 않는 아이 child_id={}", child_id); - return new BusinessException(ErrorCode.USER_NOT_FOUND); - }); - } - private static void updateNickname(AuthParentRequestDto request, Parent parent) { parent.updateNickname(request.nickname()); } diff --git a/src/main/java/com/project/growfit/domain/User/service/impl/OauthServiceImpl.java b/src/main/java/com/project/growfit/domain/User/service/impl/OauthServiceImpl.java index b82c19e..7f0b5ee 100644 --- a/src/main/java/com/project/growfit/domain/User/service/impl/OauthServiceImpl.java +++ b/src/main/java/com/project/growfit/domain/User/service/impl/OauthServiceImpl.java @@ -5,7 +5,7 @@ import com.nimbusds.jose.shaded.gson.JsonParser; import com.project.growfit.domain.User.dto.request.ParentOAuthRequestDto; import com.project.growfit.domain.User.dto.response.ParentLoginResponseDto; -import com.project.growfit.domain.User.dto.response.ParentResponse; +import com.project.growfit.domain.User.dto.response.ParentResponseDto; import com.project.growfit.domain.User.entity.Parent; import com.project.growfit.domain.User.repository.ParentRepository; import com.project.growfit.domain.User.service.OauthService; @@ -120,11 +120,12 @@ public HashMap getUserKakaoInfo(String accessToken) { } @Override + @Transactional public ResultResponse kakaoLogin(String accessToken, HttpServletResponse response) { log.info("[kakaoLogin] 카카오 로그인 요청 시작"); boolean isNewUser = false; ParentOAuthRequestDto requestDto = getUserKakaoSignupRequestDto(getUserKakaoInfo(accessToken)); - ParentResponse parentResponse = findByUserKakaoIdentifier(requestDto.id()); + ParentResponseDto parentResponse = findByUserKakaoIdentifier(requestDto.id()); if (parentResponse == null) { String email = requestDto.email(); @@ -149,7 +150,7 @@ public ResultResponse kakaoLogin(String accessToken, HttpServletResponse resp } @Override - public ResultResponse kakaoLogout(String access_token, HttpServletResponse response) { + public String kakaoLogout(String access_token, HttpServletResponse response) { Parent user = authenticatedUser.getAuthenticatedParent(); tokenRedisRepository.deleteById(user.getEmail()); @@ -164,11 +165,11 @@ public ResultResponse kakaoLogout(String access_token, HttpServletRespon } catch (HttpClientErrorException e) { System.err.println("카카오 로그아웃 실패: " + e.getMessage()); } - return ResultResponse.of(ResultCode.LOGOUT_SUCCESS, ""); + return "Parent Id [" + user.getId() + "] 로그아웃 완료"; } @Override - public ParentResponse findByUserKakaoIdentifier(String kakaoIdentifier) { + public ParentResponseDto findByUserKakaoIdentifier(String kakaoIdentifier) { log.info("[findByUserKakaoIdentifier] 카카오 ID로 부모 정보 조회: kakao_id={}", kakaoIdentifier); List parents = parentRepository.findParentByProviderId(kakaoIdentifier).orElse(List.of()); @@ -176,7 +177,7 @@ public ParentResponse findByUserKakaoIdentifier(String kakaoIdentifier) { log.warn("[findByUserKakaoIdentifier] 부모 정보 없음: kakao_id={}", kakaoIdentifier); return null; } - return new ParentResponse(parents.get(0)); + return new ParentResponseDto(parents.get(0)); } @Override @@ -201,7 +202,7 @@ private ParentOAuthRequestDto getUserKakaoSignupRequestDto(HashMap getChildByLoginId(user.getUserId()); case "ROLE_PARENT" -> { diff --git a/src/main/java/com/project/growfit/global/exception/ErrorCode.java b/src/main/java/com/project/growfit/global/exception/ErrorCode.java index bfe6771..ae2a482 100644 --- a/src/main/java/com/project/growfit/global/exception/ErrorCode.java +++ b/src/main/java/com/project/growfit/global/exception/ErrorCode.java @@ -21,6 +21,9 @@ public enum ErrorCode { USER_REGISTRATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "사용자 등록에 실패했습니다."), INVALID_CREDENTIALS(HttpStatus.UNAUTHORIZED, "아이디 또는 비밀번호가 올바르지 않습니다."), PASSWORD_UPDATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "비밀번호 변경에 실패했습니다."), + UNAUTHORIZED_CHILD(HttpStatus.UNAUTHORIZED, "로그인된 아이 정보가 없습니다."), + ALREADY_LOGGED_OUT(HttpStatus.BAD_REQUEST, "이미 로그아웃된 상태입니다."), + WEAK_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호는 영문자와 숫자를 포함해 8자 이상이어야 합니다."), CHILD_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 등록된 아이입니다."), CHILD_NOT_FOUND(HttpStatus.NOT_FOUND, "아이 정보를 찾을 수 없습니다."), @@ -50,7 +53,8 @@ public enum ErrorCode { WRONG_TARGET_TYPE(HttpStatus.BAD_REQUEST, "대상 타입이 잘못되었습니다"), //Diet - INVALID_TIME_FORMAT(HttpStatus.BAD_REQUEST, "유효하지 않은 시간 형식입니다."), + INVALID_MONTH_FORMAT(HttpStatus.BAD_REQUEST, "조회할 월의 형식이 잘못되었습니다. (yyyy-MM) 형식을 사용해주세요."), + INVALID_DATETIME_FORMAT(HttpStatus.BAD_REQUEST, "유효하지 않은 날짜 또는 시간 형식입니다."), STICKER_NOT_FOUND(HttpStatus.NOT_FOUND, "등록된 스티커가 존재하지 않습니다."), DIET_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 식단을 찾을 수 없습니다."), DAILY_DIET_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 날짜의 식단 정보가 존재하지 않습니다."), @@ -63,6 +67,13 @@ public enum ErrorCode { DIET_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 등록된 식단입니다."), INVALID_DIET_TIME_SLOT(HttpStatus.BAD_REQUEST, "잘못된 식사 시간대입니다."), DUPLICATE_MEALTYPE(HttpStatus.CONFLICT, "이미 해당 끼니의 식단이 존재합니다."), + + KEYWORD_TOO_SHORT(HttpStatus.BAD_REQUEST, "검색 키워드는 최소 2자 이상 입력해주세요."), + INVALID_FOOD_ID(HttpStatus.BAD_REQUEST, "유효하지 않은 음식 ID입니다."), + EMPTY_FOOD_LIST(HttpStatus.BAD_REQUEST, "음식 리스트는 비어 있을 수 없습니다. 음식을 한 개 이상 추가해주세요."), + EMPTY_IMAGE_FILE(HttpStatus.BAD_REQUEST, "이미지를 첨부해주세요."), + NO_IMAGE_TO_DELETE(HttpStatus.BAD_REQUEST, "삭제할 이미지가 존재하지 않습니다."), + NO_FOOD_FOR_STICKER(HttpStatus.BAD_REQUEST, "음식이 등록되어야 스티커를 남길 수 있습니다."); private final HttpStatus status; private final String message; diff --git a/src/main/java/com/project/growfit/global/response/ResultCode.java b/src/main/java/com/project/growfit/global/response/ResultCode.java index f216fac..b79683d 100644 --- a/src/main/java/com/project/growfit/global/response/ResultCode.java +++ b/src/main/java/com/project/growfit/global/response/ResultCode.java @@ -38,19 +38,18 @@ public enum ResultCode { DIET_ADD_IMAGE_SUCCESS(HttpStatus.OK, "식단 사진이 성공적으로 업로드되었습니다."), CHILD_PHOTO_DELETE_SUCCESS(HttpStatus.OK, "식단 사진을 삭제하였습니다."), - //DIET_TIME_UPDATE_SUCCESS(HttpStatus.OK, "식단 시간 수정에 성공하였습니다."), DIET_SET_LIST_SUCCESS(HttpStatus.OK, "식단 세트 목록 조회에 성공하였습니다."), DIET_SET_SAVE_SUCCESS(HttpStatus.CREATED, "식단 세트 저장에 성공하였습니다."), - //DIET_SET_DETAIL_SUCCESS(HttpStatus.OK, "식단 세트 상세 조회에 성공하였습니다."), DIET_SET_RETRIEVAL_SUCCESS(HttpStatus.OK, "식단 세트 조회에 성공했습니다."), DIET_SET_DELETE_SUCCESS(HttpStatus.OK, "식단 세트 삭제에 성공했습니다."), DIET_SET_EDIT_SUCCESS(HttpStatus.OK, "식단 세트 수정에 성공했습니다." ), + DIET_SUBMIT_SUCCESS(HttpStatus.OK, "식단 사진과 섭취 여부가 성공적으로 제출되었습니다."), DIET_OVERRIDE_SUCCESS(HttpStatus.OK, "식단 불이행 정보 입력에 성공했습니다."), CHILD_STATE_UPLOAD_SUCCESS(HttpStatus.OK, "식단 이행 상태 업데이트를 성공했습니다."), DIET_FOOD_DELETE_SUCCESS(HttpStatus.OK, "식단에서 음식이 성공적으로 삭제되었습니다."), - + DIET_DATE_EMPTY(HttpStatus.OK, "해당 날짜에 기록된 식단이 없습니다."), CALENDAR_OVERVIEW_SUCCESS(HttpStatus.OK, "식단 캘린더 조회에 성공했습니다."), diff --git a/src/test/java/com/project/growfit/domain/Diet/service/impl/DietServiceImplTest.java b/src/test/java/com/project/growfit/domain/Diet/service/impl/DietServiceImplTest.java new file mode 100644 index 0000000..2c1b0e9 --- /dev/null +++ b/src/test/java/com/project/growfit/domain/Diet/service/impl/DietServiceImplTest.java @@ -0,0 +1,218 @@ +package com.project.growfit.domain.Diet.service.impl; + +import com.project.growfit.domain.Diet.dto.request.AddDietRequestDto; +import com.project.growfit.domain.Diet.dto.request.FoodItemDto; +import com.project.growfit.domain.Diet.dto.request.UpdateDietRequestDto; +import com.project.growfit.domain.Diet.dto.request.UpdateFoodListRequestDto; +import com.project.growfit.domain.Diet.dto.response.DietBasicDto; +import com.project.growfit.domain.Diet.entity.DailyDiet; +import com.project.growfit.domain.Diet.entity.Diet; +import com.project.growfit.domain.Diet.entity.DietState; +import com.project.growfit.domain.Diet.entity.MealType; +import com.project.growfit.domain.Diet.repository.CustomFoodRepository; +import com.project.growfit.domain.Diet.repository.DailyDietRepository; +import com.project.growfit.domain.Diet.repository.DietRepository; +import com.project.growfit.domain.User.entity.Child; +import com.project.growfit.domain.User.entity.Parent; +import com.project.growfit.global.api.entity.FoodApi; +import com.project.growfit.global.api.repository.FoodApiRepository; +import com.project.growfit.global.auth.service.AuthenticatedUserProvider; +import com.project.growfit.global.exception.BusinessException; +import com.project.growfit.global.exception.ErrorCode; +import com.project.growfit.global.s3.service.S3UploadService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; + +class DietServiceImplTest { + + @InjectMocks + private DietServiceImpl dietService; + + @Mock + private FoodApiRepository foodApiRepository; + @Mock + private S3UploadService s3UploadService; + @Mock + private DailyDietRepository dailyDietRepository; + @Mock + private CustomFoodRepository customFoodRepository; + @Mock + private DietRepository dietRepository; + @Mock + private AuthenticatedUserProvider authenticatedProvider; + + private Parent mockParent; + private Child mockChild; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + mockParent = new Parent("test@email.com", "부모", null, "kakao", "id", com.project.growfit.domain.User.entity.ROLE.ROLE_PARENT); + mockChild = new Child("childId", "아이", com.project.growfit.domain.User.entity.ChildGender.MALE, 10, 120, 30, "pass", com.project.growfit.domain.User.entity.ROLE.ROLE_CHILD); + } + + @Test + @DisplayName("음식 키워드 검색 성공") + void 음식검색_성공() { + String keyword = "사과"; + FoodApi food = new FoodApi(); + ReflectionTestUtils.setField(food, "foodNm", "사과"); + Page page = new PageImpl<>(List.of(food)); + + when(authenticatedProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(foodApiRepository.findByFoodNmContaining(eq(keyword), any(PageRequest.class))).thenReturn(page); + + List result = dietService.searchFoods(keyword, 0, 10); + + assertThat(result).hasSize(1); + verify(foodApiRepository).findByFoodNmContaining(eq(keyword), any(PageRequest.class)); + } + + @Test + @DisplayName("식단 추가 성공") + void 식단추가_성공() { + FoodApi food = new FoodApi(); + ReflectionTestUtils.setField(food, "foodNm", "사과"); + + AddDietRequestDto dto = new AddDietRequestDto("2025-06-21", "12:00", MealType.LUNCH, + List.of(new FoodItemDto(1L, null, null, null, null, 1.0, 1))); + + when(authenticatedProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(authenticatedProvider.getAuthenticatedChild()).thenReturn(mockChild); + when(foodApiRepository.findById(1L)).thenReturn(Optional.of(food)); + when(dailyDietRepository.findByChildAndDate(any(), any())).thenReturn(Optional.empty()); + when(dailyDietRepository.save(any())).thenAnswer(i -> i.getArgument(0)); + + DietBasicDto result = dietService.addDiet(dto); + + assertThat(result).isNotNull(); + verify(dailyDietRepository, times(2)).save(any(DailyDiet.class)); + } + + @Test + @DisplayName("식단수정_성공") + void 식단수정_성공() { + // Given + List list = new ArrayList<>(); + FoodItemDto food = new FoodItemDto(1L, "닭가슴살", 123.0, 123.0, 123.0, 123.0, 1); + list.add(food); + UpdateDietRequestDto dto = new UpdateDietRequestDto("12:00", MealType.LUNCH, list); + Diet mockDiet = mock(Diet.class); + DailyDiet mockDailyDiet = mock(DailyDiet.class); + + when(authenticatedProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(authenticatedProvider.getAuthenticatedChild()).thenReturn(mockChild); // ⭐ + when(dietRepository.findDietWithFoodList(1L)).thenReturn(Optional.of(mockDiet)); + when(mockDiet.getChild()).thenReturn(mockChild); + when(mockDiet.getDailyDiet()).thenReturn(mockDailyDiet); + when(mockDiet.getTime()).thenReturn(LocalTime.NOON); + + when(foodApiRepository.findById(1L)).thenReturn(Optional.of(new FoodApi())); + + // When + DietBasicDto result = dietService.updateDiet(1L, dto); + + // Then + assertThat(result).isNotNull(); + verify(mockDiet).edit(anyList(), eq(dto)); + verify(mockDailyDiet).recalculate(); + } + + @Test + @DisplayName("식단 삭제 성공") + void 식단삭제_성공() { + Diet mockDiet = mock(Diet.class); + when(mockDiet.getChild()).thenReturn(mockChild); + + when(authenticatedProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(authenticatedProvider.getAuthenticatedChild()).thenReturn(mockChild); + when(dietRepository.findDietWithFoodList(1L)).thenReturn(Optional.of(mockDiet)); + when(mockDiet.getTime()).thenReturn(LocalTime.NOON); + + DietBasicDto result = dietService.deleteDiet(1L); + + assertThat(result).isNotNull(); + verify(dietRepository).delete(mockDiet); + } + + @Test + @DisplayName("식단 오버라이드 성공") + void 식단오버라이드_성공() { + FoodApi food = new FoodApi(); + ReflectionTestUtils.setField(food, "foodNm", "바나나"); + + Diet mockDiet = mock(Diet.class); + DailyDiet mockDaily = mock(DailyDiet.class); + when(mockDiet.getDailyDiet()).thenReturn(mockDaily); + when(mockDiet.getChild()).thenReturn(mockChild); + when(mockDiet.getTime()).thenReturn(LocalTime.NOON); + + when(authenticatedProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(authenticatedProvider.getAuthenticatedChild()).thenReturn(mockChild); + when(dietRepository.findDietWithFoodList(1L)).thenReturn(Optional.of(mockDiet)); + when(foodApiRepository.findById(anyLong())).thenReturn(Optional.of(food)); + + UpdateFoodListRequestDto dto = new UpdateFoodListRequestDto(List.of(new FoodItemDto(1L, null, null, null, null, 1.0, 1))); + + DietBasicDto result = dietService.overrideDietNutrition(1L, dto); + + assertThat(result).isNotNull(); + verify(mockDiet).edit(any()); + verify(mockDaily).recalculate(); + } + + @Test + @DisplayName("식단 사진 제출 실패 - 이미지 없음") + void 식단사진제출_실패_이미지없음() { + Diet mockDiet = mock(Diet.class); + when(mockDiet.getChild()).thenReturn(mockChild); + when(authenticatedProvider.getAuthenticatedChild()).thenReturn(mockChild); + when(dietRepository.findDietWithFoodList(1L)).thenReturn(Optional.of(mockDiet)); + + assertThatThrownBy(() -> dietService.submitDiet(1L, null, DietState.MATCH)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.EMPTY_IMAGE_FILE.getMessage()); + } + @Test + @DisplayName("식단 상세 조회 성공") + void 식단상세조회_성공() { + Diet mockDiet = mock(Diet.class); + when(mockDiet.getChild()).thenReturn(mockChild); + when(mockDiet.getFoodList()).thenReturn(Collections.emptyList()); + when(mockDiet.getId()).thenReturn(1L); + when(mockDiet.getImageUrl()).thenReturn("url"); + when(mockDiet.getTime()).thenReturn(LocalTime.NOON); + when(mockDiet.getState()).thenReturn(DietState.MATCH); + when(mockDiet.getTotalCalorie()).thenReturn(100.0); + when(mockDiet.getTotalCarbohydrate()).thenReturn(10.0); + when(mockDiet.getTotalProtein()).thenReturn(10.0); + when(mockDiet.getTotalFat()).thenReturn(5.0); + + when(authenticatedProvider.getAuthenticatedChild()).thenReturn(mockChild); + when(dietRepository.findDietWithFoodList(1L)).thenReturn(Optional.of(mockDiet)); + + var result = dietService.getDietDetail(1L); + + assertThat(result).isNotNull(); + assertThat(result.dietId()).isEqualTo(1L); + assertThat(result.imageUrl()).isEqualTo("url"); + } +} diff --git a/src/test/java/com/project/growfit/domain/Diet/service/impl/DietSetServiceImplTest.java b/src/test/java/com/project/growfit/domain/Diet/service/impl/DietSetServiceImplTest.java new file mode 100644 index 0000000..ddaf592 --- /dev/null +++ b/src/test/java/com/project/growfit/domain/Diet/service/impl/DietSetServiceImplTest.java @@ -0,0 +1,74 @@ +package com.project.growfit.domain.Diet.service.impl; + + +import com.project.growfit.domain.Diet.dto.request.SaveDietSetRequestDto; +import com.project.growfit.domain.Diet.dto.request.FoodItemDto; +import com.project.growfit.domain.Diet.dto.response.DietSetBasicDto; +import com.project.growfit.domain.Diet.entity.DietSet; +import com.project.growfit.domain.Diet.repository.CustomFoodRepository; +import com.project.growfit.domain.Diet.repository.DietSetRepository; +import com.project.growfit.domain.User.entity.Parent; +import com.project.growfit.global.api.repository.FoodApiRepository; +import com.project.growfit.global.auth.service.AuthenticatedUserProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class DietSetServiceImplTest { + + @Mock private DietSetRepository dietSetRepository; + @Mock private CustomFoodRepository customFoodRepository; + @Mock private FoodApiRepository foodApiRepository; + @Mock private AuthenticatedUserProvider authenticatedProvider; + @Mock private Parent mockParent; + + @InjectMocks + private DietSetServiceImpl dietSetService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("식단 세트 저장 성공") + void saveDietSetSuccess() { + // Given + when(authenticatedProvider.getAuthenticatedParent()).thenReturn(mockParent); + SaveDietSetRequestDto dto = new SaveDietSetRequestDto("세트A", List.of( + new FoodItemDto(null, "사과", 52.0, 14.0, 0.2, 0.3, 1) + )); + + // When + DietSetBasicDto result = dietSetService.saveDietSet(dto); + + // Then + assertThat(result).isNotNull(); + verify(dietSetRepository, times(1)).save(any(DietSet.class)); + } + + @Test + @DisplayName("식단 세트 삭제 실패 - 접근 권한 없음") + void deleteDietSetForbidden() { + // Given + DietSet fakeSet = mock(DietSet.class); + when(authenticatedProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(fakeSet.getParent()).thenReturn(mock(Parent.class)); + when(dietSetRepository.findById(1L)).thenReturn(Optional.of(fakeSet)); + + // When / Then + assertThatThrownBy(() -> dietSetService.deleteDietSet(1L)) + .isInstanceOf(RuntimeException.class); // 실제 BusinessException으로 바꿔도 됨 + } +} \ No newline at end of file diff --git a/src/test/java/com/project/growfit/domain/User/repository/ChildRepositoryTest.java b/src/test/java/com/project/growfit/domain/User/repository/ChildRepositoryTest.java index 53610d0..01af49b 100644 --- a/src/test/java/com/project/growfit/domain/User/repository/ChildRepositoryTest.java +++ b/src/test/java/com/project/growfit/domain/User/repository/ChildRepositoryTest.java @@ -1,7 +1,7 @@ package com.project.growfit.domain.User.repository; -import com.project.growfit.domain.User.entity.Child; -import com.project.growfit.domain.User.entity.ROLE; +import com.project.growfit.domain.User.entity.*; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -20,15 +20,30 @@ public class ChildRepositoryTest { @Autowired private ChildRepository childRepository; + @Autowired + private ParentRepository parentRepository; + + private Parent savedParent; + private Child savedChild; + + @BeforeEach + void setUp() { + // 부모 저장 + Parent parent = new Parent("parent@test.com", "부모", null, "kakao", "pid", ROLE.ROLE_PARENT); + savedParent = parentRepository.save(parent); + + Child child = new Child("child123", "김아이", ChildGender.MALE, 10, 120, 50, "password123", ROLE.ROLE_CHILD); + savedParent.addChild(child); // 연관관계 설정 + savedChild = childRepository.save(child); + child.updateCode("codeXYZ"); + } + @Test @DisplayName("[findByChildId 성공 테스트] 존재하는 Child 엔티티 반환") void testFindByChildId() { - Child child = new Child("child123", "1234", "테스트", ROLE.ROLE_CHILD); - childRepository.save(child); - // When Optional result = childRepository.findByLoginId("child123"); - // Then + assertTrue(result.isPresent(), "Child 엔티티가 존재해야 합니다."); assertEquals("child123", result.get().getLoginId()); } @@ -36,27 +51,19 @@ void testFindByChildId() { @Test @DisplayName("[existsByCodeAndChildId 성공 테스트] 해당 code와 childId 조합의 존재 여부 확인") void testExistsByCodeAndChildId() { - Child child = new Child("child123", "1234", "테스트", ROLE.ROLE_CHILD); - ReflectionTestUtils.setField(child, "codeNumber", "codeXYZ"); - childRepository.save(child); - boolean exists = childRepository.existsByCodeNumberAndLoginId("codeXYZ", "child123"); assertTrue(exists, "해당 code와 childId 조합이 존재해야 합니다."); } + @Test @DisplayName("[findByCode 성공 테스트] code로 Child 엔티티 조회") void testFindByCode() { - Child child = new Child("child123", "1234", "테스트", ROLE.ROLE_CHILD); - ReflectionTestUtils.setField(child, "codeNumber", "codeXYZ"); - childRepository.save(child); - Optional result = childRepository.findByCodeNumber("codeXYZ"); assertTrue(result.isPresent(), "Child 엔티티가 존재해야 합니다."); assertEquals("codeXYZ", ReflectionTestUtils.getField(result.get(), "codeNumber")); } - } diff --git a/src/test/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImplTest.java b/src/test/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImplTest.java index 19b0b5c..5eb8b77 100644 --- a/src/test/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImplTest.java +++ b/src/test/java/com/project/growfit/domain/User/service/impl/AuthChildServiceImplTest.java @@ -3,6 +3,7 @@ import com.project.growfit.domain.User.dto.request.AuthChildRequestDto; import com.project.growfit.domain.User.dto.request.FindChildPasswordRequestDto; import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto; +import com.project.growfit.domain.User.dto.response.ChildResponseDto; import com.project.growfit.domain.User.entity.Child; import com.project.growfit.domain.User.entity.ROLE; import com.project.growfit.domain.User.repository.ChildRepository; @@ -65,6 +66,12 @@ class AuthChildServiceImplTest { private Child mockChild; + + String loginId = "childTestId"; + String password = "password123"; + String nickname = "민준콩"; + String code = "testCode"; + @BeforeEach void setUp() { mockChild = new Child("minjun", "김민준", null, 10, 130, 30, "testCode", null); @@ -74,15 +81,14 @@ void setUp() { @DisplayName("[findChildID 성공 테스트] - 아이 ID 찾기 성공") void 알맞은_코드를_입력_시_아이_정보를_반환한다() { // Given - String code = "testCode"; when(childRepository.findByCodeNumber(code)).thenReturn(Optional.of(mockChild)); // When - ResultResponse response = authChildService.findChildID(code); + ChildResponseDto result = authChildService.findChildID(code); // Then - assertThat(response).isNotNull(); - assertThat(response.getData()).isInstanceOf(ChildInfoResponseDto.class); + assertThat(result).isNotNull(); + assertThat(result.child_login_id()).isEqualTo(mockChild.getLoginId()); verify(childRepository, times(1)).findByCodeNumber(code); } @@ -90,7 +96,6 @@ void setUp() { @DisplayName("[findByCode 실패 테스트] 아이를 찾을 수 없음") void 틀린_코드를_입력_시_아이_정보를_반환하지_않는다() { // Given - String code = "invalidCode"; when(childRepository.findByCodeNumber(code)).thenReturn(Optional.empty()); // When & Then @@ -106,15 +111,15 @@ void setUp() { void 아이_계졍을_성공적으로_등록한다() { // Given Long childId = 1L; - AuthChildRequestDto request = new AuthChildRequestDto("childTestId", "password123", "민준콩"); + AuthChildRequestDto request = new AuthChildRequestDto(loginId, password, nickname); when(childRepository.findById(childId)).thenReturn(Optional.of(mockChild)); when(passwordEncoder.encode(request.childPassword())).thenReturn("encodedPassword"); // When - ResultResponse response = authChildService.registerChildCredentials(childId, request); + ChildInfoResponseDto dto = authChildService.registerChildCredentials(childId, request); // Then - assertThat(response).isNotNull(); + assertThat(dto).isNotNull(); verify(childRepository, times(1)).save(mockChild); } @@ -123,7 +128,7 @@ void setUp() { void 아이_정보가_없는_경우_아이_등록에_실패한다() { // Given Long childId = 999L; - AuthChildRequestDto request = new AuthChildRequestDto("childTestId", "password123", "민준콩"); + AuthChildRequestDto request = new AuthChildRequestDto(loginId, password, nickname); when(childRepository.findById(childId)).thenReturn(Optional.empty()); // When & Then @@ -138,23 +143,26 @@ void setUp() { @DisplayName("[findChildPassword 성공 테스트] 비밀번호 변경 성공") void 아이의_비밀번호를_성공적으로_변경한다() { // Given - FindChildPasswordRequestDto request = new FindChildPasswordRequestDto("childTestId", "testCode", "newPassword"); - when(childRepository.existsByCodeNumberAndLoginId(request.code(), request.user_id())).thenReturn(true); - when(childRepository.findByCodeNumber(request.code())).thenReturn(Optional.of(mockChild)); + FindChildPasswordRequestDto request = new FindChildPasswordRequestDto(loginId, code, "newpassword1234"); + when(childRepository.existsByCodeNumberAndLoginId(code, loginId)).thenReturn(true); + when(childRepository.findByCodeNumber(code)).thenReturn(Optional.of(mockChild)); + when(passwordEncoder.encode("newpassword1234")).thenReturn("encodedPassword"); // When - ResultResponse response = authChildService.findChildPassword(request); + ChildInfoResponseDto dto = authChildService.findChildPassword(request); // Then - assertThat(response).isNotNull(); + assertThat(dto).isNotNull(); + assertThat(dto.child_name()).isEqualTo(mockChild.getName()); verify(childRepository, times(1)).save(mockChild); + verify(passwordEncoder, times(1)).encode("newpassword1234"); } @Test @DisplayName("[findChildPassword 실패 테스트] 비밀번호 변경 실패") void 아이의_비밀번호_변경을_실패한다() { // Given - FindChildPasswordRequestDto request = new FindChildPasswordRequestDto("childTestId", "invalidCode", "newPassword"); + FindChildPasswordRequestDto request = new FindChildPasswordRequestDto(loginId, code, "newpassword1234"); when(childRepository.existsByCodeNumberAndLoginId(request.code(), request.user_id())).thenReturn(false); // When & Then @@ -169,24 +177,27 @@ void setUp() { @DisplayName("[login 성공 테스트] 아이 로그인 성공") void 아이_로그인에_성공한다() { // Given - AuthChildRequestDto request = new AuthChildRequestDto("childTestId", "password123", null); - Child mockChild = new Child("childTestId", "닉네임", "encodedPassword", ROLE.ROLE_CHILD); + AuthChildRequestDto request = new AuthChildRequestDto(loginId, password, nickname); + Child mockChild = new Child(loginId, nickname, password, ROLE.ROLE_CHILD); Authentication mockAuthentication = mock(Authentication.class); when(authenticationProvider.authenticate(any())).thenReturn(mockAuthentication); - when(childRepository.findByLoginId("childTestId")).thenReturn(Optional.of(mockChild)); - when(jwtProvider.createAccessToken(any(), any(), any())).thenReturn("access-token"); - when(jwtProvider.createRefreshToken(any())).thenReturn("refresh-token"); + when(childRepository.findByLoginId(loginId)).thenReturn(Optional.of(mockChild)); + when(jwtProvider.createAccessToken(eq(loginId), eq("ROLE_CHILD"), any())).thenReturn("access-token"); + when(jwtProvider.createRefreshToken(eq(loginId))).thenReturn("refresh-token"); // When - ResultResponse result = authChildService.login(request, response); + ChildResponseDto dto = authChildService.login(request, response); // Then - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo(ResultCode.LOGIN_SUCCESS.getStatus().value()); + assertThat(dto).isNotNull(); + assertThat(dto.child_login_id()).isEqualTo(loginId); + verify(authenticationProvider, times(1)).authenticate(any()); - verify(childRepository, times(1)).findByLoginId("childTestId"); + verify(childRepository, times(1)).findByLoginId(loginId); + verify(jwtProvider).createAccessToken(loginId, "ROLE_CHILD", "LOCAL"); + verify(jwtProvider).createRefreshToken(loginId); verify(tokenRedisRepository, times(1)).save(any(TokenRedis.class)); verify(cookieService, times(1)).saveAccessTokenToCookie(response, "access-token"); } @@ -195,17 +206,19 @@ void setUp() { @DisplayName("[logout 성공 테스트] 아이 로그아웃 성공") void 아이_로그아웃을_성공한다() { // Given - String loginId = "childTestId"; - Child mockChild = new Child(loginId, "닉네임", "암호화된비밀번호", ROLE.ROLE_CHILD); + Child mockChild = new Child(loginId, nickname, password, ROLE.ROLE_CHILD); when(authenticatedUser.getAuthenticatedChild()).thenReturn(mockChild); - doNothing().when(tokenRedisRepository).deleteById(loginId); - doNothing().when(cookieService).clearCookie(response, "accessToken"); + doNothing().when(tokenRedisRepository).deleteById(eq(loginId)); + doNothing().when(cookieService).clearCookie(eq(response), eq("accessToken")); - ResultResponse result = authChildService.logout(response); + // When + ChildResponseDto dto = authChildService.logout(response); + + // Then + assertThat(dto).isNotNull(); + assertThat(dto.child_login_id()).isEqualTo(loginId); - assertThat(result).isNotNull(); - assertThat(result.getStatus()).isEqualTo(200); verify(authenticatedUser, times(1)).getAuthenticatedChild(); verify(tokenRedisRepository, times(1)).deleteById(loginId); verify(cookieService, times(1)).clearCookie(response, "accessToken"); diff --git a/src/test/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImplTest.java b/src/test/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImplTest.java index 3e0b895..0ba7872 100644 --- a/src/test/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImplTest.java +++ b/src/test/java/com/project/growfit/domain/User/service/impl/AuthParentServiceImplTest.java @@ -1,18 +1,17 @@ package com.project.growfit.domain.User.service.impl; import com.project.growfit.domain.User.dto.request.AuthParentRequestDto; +import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto; +import com.project.growfit.domain.User.dto.response.ChildQrCodeResponseDto; import com.project.growfit.domain.User.entity.Child; import com.project.growfit.domain.User.entity.Parent; import com.project.growfit.domain.User.entity.ROLE; import com.project.growfit.domain.User.repository.ChildRepository; import com.project.growfit.domain.User.repository.ParentRepository; -import com.project.growfit.domain.User.service.AuthParentService; import com.project.growfit.global.auth.dto.CustomUserDetails; import com.project.growfit.global.auth.service.AuthenticatedUserProvider; import com.project.growfit.global.exception.BusinessException; import com.project.growfit.global.exception.ErrorCode; -import com.project.growfit.global.response.ResultCode; -import com.project.growfit.global.response.ResultResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -55,15 +54,16 @@ void setUp() { @Test @DisplayName("자녀 등록 성공 테스트") void 자녀_등록을_성공한다() { - AuthParentRequestDto request = new AuthParentRequestDto("김아이","아이", MALE, 10, 130, 30); - when(parentRepository.findByEmail(anyString())).thenReturn(Optional.of(mockParent)); + // Given + AuthParentRequestDto request = new AuthParentRequestDto("김아이", "아이", MALE, 10, 130, 30); + when(authenticatedUserProvider.getAuthenticatedParent()).thenReturn(mockParent); // When - ResultResponse response = authParentService.registerChild(mockUser, request); + ChildInfoResponseDto dto = authParentService.registerChild(request); // Then - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(ResultCode.SIGNUP_SUCCESS.getStatus().value()); + assertThat(dto).isNotNull(); + assertThat(dto.child_name()).isEqualTo("아이"); verify(childRepository, times(1)).save(any(Child.class)); } @@ -71,12 +71,14 @@ void setUp() { @DisplayName("중복 자녀 등록 실패 테스트") void 중복_자녀를_등록_시_자녀_등록에_실패한다() { // Given - AuthParentRequestDto request = new AuthParentRequestDto("아이","김아이", MALE, 10, 130, 30); + AuthParentRequestDto request = new AuthParentRequestDto("아이", "김아이", MALE, 10, 130, 30); mockParent.addChild(new Child(null, "김아이", MALE, 10, 130, 30, null, ROLE.ROLE_CHILD)); - when(parentRepository.findByEmail(anyString())).thenReturn(Optional.of(mockParent)); + + // Mock 설정 추가 + when(authenticatedUserProvider.getAuthenticatedParent()).thenReturn(mockParent); // When & Then - assertThatThrownBy(() -> authParentService.registerChild(mockUser, request)) + assertThatThrownBy(() -> authParentService.registerChild(request)) .isInstanceOf(BusinessException.class) .hasMessage(ErrorCode.CHILD_ALREADY_EXISTS.getMessage()); } @@ -85,28 +87,30 @@ void setUp() { @DisplayName("QR 생성 성공 테스트") void 자녀_등록_QR을_생성한다() throws Exception { // Given - Child mockChild = new Child("childId", "nickname", "pass", ROLE.ROLE_CHILD); - when(authenticatedUserProvider.getAuthenticatedChild()).thenReturn(mockChild); + Child child = new Child("childId", "nickname", "pass", ROLE.ROLE_CHILD); + when(authenticatedUserProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(authenticatedUserProvider.getAuthenticatedChild()).thenReturn(child); // When - ResultResponse response = authParentService.createQR(mockUser); + ChildQrCodeResponseDto dto = authParentService.createQR(); // Then - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(ResultCode.QR_GENERATION_SUCCESS.getStatus().value()); - verify(childRepository, times(0)).save(any()); // QR에서는 저장 로직 없음 + assertThat(dto).isNotNull(); + assertThat(dto.qr_code()).isNotBlank(); + assertThat(dto.code()).isNotBlank(); } @Test @DisplayName("QR 중복 생성 실패 테스트") void 이미_QR코드를_생성한_경우_실패한다() { // Given - Child mockChild = new Child("childId", "nickname", "pass", ROLE.ROLE_CHILD); - mockChild.updateCode("existing-code"); - when(authenticatedUserProvider.getAuthenticatedChild()).thenReturn(mockChild); + Child child = new Child("childId", "nickname", "pass", ROLE.ROLE_CHILD); + child.updateCode("existing-code"); + when(authenticatedUserProvider.getAuthenticatedParent()).thenReturn(mockParent); + when(authenticatedUserProvider.getAuthenticatedChild()).thenReturn(child); // When & Then - assertThatThrownBy(() -> authParentService.createQR(mockUser)) + assertThatThrownBy(() -> authParentService.createQR()) .isInstanceOf(BusinessException.class) .hasMessage(ErrorCode.QR_ALREADY_EXISTS.getMessage()); } diff --git a/src/test/java/com/project/growfit/domain/User/service/impl/OauthServiceImplTest.java b/src/test/java/com/project/growfit/domain/User/service/impl/OauthServiceImplTest.java index 71d8da5..ecc89e9 100644 --- a/src/test/java/com/project/growfit/domain/User/service/impl/OauthServiceImplTest.java +++ b/src/test/java/com/project/growfit/domain/User/service/impl/OauthServiceImplTest.java @@ -1,7 +1,7 @@ package com.project.growfit.domain.User.service.impl; import com.project.growfit.domain.User.dto.request.ParentOAuthRequestDto; -import com.project.growfit.domain.User.dto.response.ParentResponse; +import com.project.growfit.domain.User.dto.response.ParentResponseDto; import com.project.growfit.domain.User.entity.Parent; import com.project.growfit.domain.User.entity.ROLE; import com.project.growfit.domain.User.repository.ParentRepository; @@ -9,7 +9,6 @@ import com.project.growfit.global.auth.jwt.JwtProvider; import com.project.growfit.global.auth.service.AuthenticatedUserProvider; import com.project.growfit.global.redis.repository.TokenRedisRepository; -import com.project.growfit.global.response.ResultResponse; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -73,10 +72,9 @@ void setup() { when(restTemplate.postForEntity(anyString(), any(), eq(String.class))) .thenReturn(new ResponseEntity<>(HttpStatus.OK)); - ResultResponse result = oauthService.kakaoLogout("dummyAccessToken", response); + String str = oauthService.kakaoLogout("dummyAccessToken", response); - assertEquals(HttpStatus.OK.value(), result.getStatus()); - verify(tokenRedisRepository).deleteById("1"); + assertEquals(str, "Parent Id [" + parent.getId() + "] 로그아웃 완료"); verify(cookieService).clearCookie(response, "accessToken"); } @@ -85,7 +83,7 @@ void setup() { void 카카오_식별자로_부모를_조회할_때_존재하면_응답을_반환한다() { Parent parent = new Parent("test@example.com", "name", "photo", "kakao", "kid", ROLE.ROLE_PARENT); when(parentRepository.findParentByProviderId("kid")).thenReturn(Optional.of(List.of(parent))); - ParentResponse response = oauthService.findByUserKakaoIdentifier("kid"); + ParentResponseDto response = oauthService.findByUserKakaoIdentifier("kid"); assertNotNull(response); assertEquals("test@example.com", response.email()); } @@ -93,7 +91,7 @@ void setup() { @Test void 카카오_식별자로_부모를_조회할_때_존재하지_않으면_null을_반환한다() { when(parentRepository.findParentByProviderId("kid")).thenReturn(Optional.of(List.of())); - ParentResponse response = oauthService.findByUserKakaoIdentifier("kid"); + ParentResponseDto response = oauthService.findByUserKakaoIdentifier("kid"); assertNull(response); } } \ No newline at end of file