Skip to content
Merged
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ 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') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.project.growfit.domain.User.controller;

import com.project.growfit.domain.User.dto.request.ChildInfoRequestDto;
import com.project.growfit.domain.User.dto.request.ParentInfoRequestDto;
import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto;
import com.project.growfit.domain.User.dto.response.ParentInfoResponseDto;
import com.project.growfit.domain.User.service.UserService;
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 lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
@Tag(name = "유저 정보 API")
public class UserController {

private final UserService userService;

@Operation(summary = "부모 정보 조회")
@GetMapping("/parent")
public ResultResponse<ParentInfoResponseDto> getParentInfo() {
ParentInfoResponseDto dto = userService.getParentInfo();
return ResultResponse.of(ResultCode.INFO_SUCCESS, dto);
}

@Operation(summary = "아이 정보 조회")
@GetMapping("/child")
public ResultResponse<ChildInfoResponseDto> getChildInfo() {
ChildInfoResponseDto dto = userService.getChildInfo();
return ResultResponse.of(ResultCode.INFO_SUCCESS, dto);
}

@Operation(summary = "부모 정보 수정 (이메일 수정 시 JWT 재발급)")
@PutMapping(value = "/parent", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResultResponse<String> updateParentInfo(@Valid @RequestPart("info") ParentInfoRequestDto request,
@RequestPart(value = "image", required = false) MultipartFile image,
HttpServletResponse response
) throws IOException {
userService.updateParentInfo(request, image, response);
return ResultResponse.of(ResultCode.UPDATE_SUCCESS, "부모 정보가 수정되었습니다.");
}

@Operation(summary = "아이 정보 수정")
@PutMapping("/child")
public ResultResponse<String> updateChildInfo(@Valid @RequestBody ChildInfoRequestDto request) {
userService.updateChildInfo(request);
return ResultResponse.of(ResultCode.UPDATE_SUCCESS, "아이 정보가 수정되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.project.growfit.domain.User.dto.request;

import com.project.growfit.domain.User.entity.ChildGender;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;

public record ChildInfoRequestDto(
@NotBlank(message = "닉네임은 필수입니다.")
@Schema(description = "자녀 닉네임", example = "민준이")
String nickname,

@Pattern(
regexp = "^(?=.*[a-z])(?=.*\\d)[a-z\\d]{8,}$",
message = "비밀번호는 소문자와 숫자를 포함해 8자 이상이어야 합니다."
)
@Schema(description = "자녀 비밀번호", example = "secure123")
String password,

@NotNull(message = "성별을 입력해주세요.")
@Schema(description = "자녀 성별", example = "MALE")
ChildGender gender,

@Min(value = 1, message = "나이는 1세 이상이어야 합니다.")
@Schema(description = "자녀 나이", example = "11")
int age,

@Schema(description = "아이 키 (cm)", example = "110")
@Min(value = 30, message = "키는 30cm 이상이어야 합니다.")
@Max(value = 250, message = "키는 200cm 이하이어야 합니다.")
long height,

@Schema(description = "아이 몸무게 (kg)", example = "20")
@Min(value = 5, message = "몸무게는 5kg 이상이어야 합니다.")
@Max(value = 150, message = "몸무게는 150kg 이하이어야 합니다.")
long weight
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.project.growfit.domain.User.dto.request;

import com.project.growfit.domain.User.entity.ChildGender;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;

public record ParentInfoRequestDto(

@NotBlank(message = "부모 닉네임은 필수입니다.")
@Schema(description = "부모 닉네임", example = "민준맘")
String nickname,

@Email(message = "유효한 이메일 형식이어야 합니다.")
@NotBlank(message = "이메일은 필수입니다.")
@Schema(description = "부모 이메일", example = "[email protected]")
String email,

@NotBlank(message = "자녀 이름은 필수입니다.")
@Schema(description = "자녀 이름", example = "김민준")
String childName,

@Min(value = 1, message = "자녀 나이는 1세 이상이어야 합니다.")
@Schema(description = "자녀 나이", example = "11")
int childAge,

@NotNull(message = "자녀 성별은 필수입니다.")
@Schema(description = "자녀 성별", example = "MALE")
ChildGender childGender,

@Schema(description = "아이 키 (cm)", example = "110")
@Min(value = 30, message = "키는 30cm 이상이어야 합니다.")
@Max(value = 250, message = "키는 200cm 이하이어야 합니다.")
long childHeight,

@Schema(description = "아이 몸무게 (kg)", example = "20")
@Min(value = 5, message = "몸무게는 5kg 이상이어야 합니다.")
@Max(value = 150, message = "몸무게는 150kg 이하이어야 합니다.")
long childWeight
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ public record ChildInfoResponseDto(
ChildGender child_gender,
int child_age,
ChildBodyInfoResponseDto child_BodyInfo

) {
public static ChildInfoResponseDto toDto(Child child) {
return new ChildInfoResponseDto(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.project.growfit.domain.User.dto.response;

import com.project.growfit.domain.User.entity.Parent;

public record ParentInfoResponseDto(
String nickname,
String profileImage,
ChildInfoResponseDto child
) {
public static ParentInfoResponseDto toDto(Parent parent) {
return new ParentInfoResponseDto(
parent.getNickname(),
parent.getProviderId(),
ChildInfoResponseDto.toDto(parent.getChildren().get(0))
);
}
}
11 changes: 9 additions & 2 deletions src/main/java/com/project/growfit/domain/User/entity/Child.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ public String user_password() {

public void addRegister(Parent parent) {this.parent = parent;}
public void updateCode(String code) {this.codeNumber = code;}
public void updateNickname(String nickname){this.nickname = nickname;}
public void updatePassword(String password){
this.password = password;}

Expand All @@ -119,7 +118,15 @@ public void updateCredentials(String id, String password, String nickname) {
}

public ChildBodyInfo getLatestBodyInfo() {
return bodyInfoList.isEmpty() ? null : bodyInfoList.get(0);
return bodyInfoList.isEmpty() ? null : bodyInfoList.getLast();
}

public void updateInfo(int age, ChildGender gender, String name, String nickname, long height, long weight) {
this.age = age;
this.gender = gender;
if (name != null) this.name = name;
if (nickname != null) this.nickname = nickname;
this.bodyInfoList.add(new ChildBodyInfo(height, weight, this));
}

}
11 changes: 10 additions & 1 deletion src/main/java/com/project/growfit/domain/User/entity/Parent.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,19 @@ public boolean hasChildWithName(String childName) {
.anyMatch(child -> child.getName().equals(childName));
}

public void updateNickname(String nickname){
public void updateInfo(String nickname){
this.nickname = nickname;
}

public void updateInfo(String nickname, String profile){
this.nickname = nickname;
this.photo = profile;
}

public void updateEmail(String email){
this.email = email;
}

public void addChild(Child child) {
this.children.add(child);
child.addRegister(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.project.growfit.domain.User.service;

import com.project.growfit.domain.User.dto.request.ChildInfoRequestDto;
import com.project.growfit.domain.User.dto.request.ParentInfoRequestDto;
import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto;
import com.project.growfit.domain.User.dto.response.ParentInfoResponseDto;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;

public interface UserService {

ParentInfoResponseDto getParentInfo();
ChildInfoResponseDto getChildInfo();

void updateParentInfo(ParentInfoRequestDto request, MultipartFile image, HttpServletResponse response);
void updateChildInfo(ChildInfoRequestDto request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.project.growfit.domain.User.service.impl;

import com.project.growfit.domain.User.dto.request.ChildInfoRequestDto;
import com.project.growfit.domain.User.dto.request.ParentInfoRequestDto;
import com.project.growfit.domain.User.dto.response.ChildInfoResponseDto;
import com.project.growfit.domain.User.dto.response.ParentInfoResponseDto;
import com.project.growfit.domain.User.entity.Child;
import com.project.growfit.domain.User.entity.Parent;
import com.project.growfit.domain.User.service.UserService;
import com.project.growfit.global.auth.jwt.JwtProvider;
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 jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

private final AuthenticatedUserProvider userProvider;
private final S3UploadService s3UploadService;
private final JwtProvider jwtProvider;
private final PasswordEncoder passwordEncoder;

private String imageUploadPath = "user/";

@Override
@Transactional(readOnly = true)
public ParentInfoResponseDto getParentInfo() {
Parent parent = userProvider.getAuthenticatedParent();
return ParentInfoResponseDto.toDto(parent);
}

@Override
@Transactional(readOnly = true)
public ChildInfoResponseDto getChildInfo() {
Child child = userProvider.getAuthenticatedChild();
return ChildInfoResponseDto.toDto(child);
}


@Override
@Transactional
public void updateParentInfo(ParentInfoRequestDto request, MultipartFile image, HttpServletResponse response) {
Parent parent = userProvider.getAuthenticatedParent();

if (image == null || image.isEmpty()) throw new BusinessException(ErrorCode.EMPTY_IMAGE_FILE);
String imageUrl;
try {
imageUrl = s3UploadService.saveFile(image, imageUploadPath);
} catch (Exception e) {
throw new BusinessException(ErrorCode.FILE_UPLOAD_ERROR);
}

boolean isEmailChanged = !parent.getEmail().equals(request.email());
String oldEmail = parent.getEmail();
parent.updateInfo(request.nickname(), imageUrl);
if (isEmailChanged) parent.updateEmail(request.email());

Child child = parent.getChildren().get(0);
child.updateInfo(request.childAge(), request.childGender(), request.childName(), null, request.childHeight(), request.childWeight());

if (isEmailChanged) jwtProvider.regenerateToken(oldEmail, request.email(), String.valueOf(parent.getRole()), "SOCIAL_KAKAO", response);
}

@Override
@Transactional
public void updateChildInfo(ChildInfoRequestDto request) {
Child child = userProvider.getAuthenticatedChild();
if (request.password() != null &&
!request.password().isBlank() &&
!passwordEncoder.matches(request.password(), child.getPassword())) {

String encodedPassword = passwordEncoder.encode(request.password());
child.updatePassword(encodedPassword);
}
child.updateInfo(request.age(), request.gender(), null, request.nickname(), request.height(), request.weight());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.project.growfit.domain.User.controller;
package com.project.growfit.domain.auth.controller;

import com.project.growfit.domain.User.dto.request.AuthChildRequestDto;
import com.project.growfit.domain.User.dto.request.FindChildPasswordRequestDto;
import com.project.growfit.domain.auth.dto.request.AuthChildRequestDto;
import com.project.growfit.domain.auth.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.domain.auth.dto.response.ChildResponseDto;
import com.project.growfit.domain.auth.service.AuthChildService;
import com.project.growfit.global.response.ResultCode;
import com.project.growfit.global.response.ResultResponse;
import io.swagger.v3.oas.annotations.Operation;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.project.growfit.domain.User.controller;
package com.project.growfit.domain.auth.controller;

import com.google.zxing.WriterException;
import com.project.growfit.domain.User.dto.request.AuthParentRequestDto;
import com.project.growfit.domain.auth.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.domain.auth.dto.response.ChildQrCodeResponseDto;
import com.project.growfit.domain.auth.service.AuthParentService;
import com.project.growfit.global.auth.dto.CustomUserDetails;
import com.project.growfit.global.response.ResultCode;
import com.project.growfit.global.response.ResultResponse;
Expand Down Expand Up @@ -35,7 +35,7 @@ public ResultResponse<ChildInfoResponseDto> registerChild(@Valid @RequestBody Au

@Operation(summary = "아이 QR 코드 생성")
@GetMapping("/child/qr")
public ResultResponse<ChildQrCodeResponseDto> createQrCode(@AuthenticationPrincipal CustomUserDetails user) throws WriterException {
public ResultResponse<ChildQrCodeResponseDto> createQrCode() throws WriterException {
ChildQrCodeResponseDto dto = parentService.createQR();

return ResultResponse.of(ResultCode.QR_GENERATION_SUCCESS, dto);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.project.growfit.domain.User.controller;
package com.project.growfit.domain.auth.controller;

import com.project.growfit.domain.User.dto.response.ParentLoginResponseDto;
import com.project.growfit.domain.User.service.OauthService;
import com.project.growfit.domain.auth.dto.response.ParentLoginResponseDto;
import com.project.growfit.domain.auth.service.OauthService;
import com.project.growfit.global.exception.BusinessException;
import com.project.growfit.global.response.ResultCode;
import com.project.growfit.global.response.ResultResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.project.growfit.domain.User.controller;
package com.project.growfit.domain.auth.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.project.growfit.domain.User.controller;
package com.project.growfit.domain.auth.controller;

import com.project.growfit.domain.User.dto.response.ParentLoginResponseDto;
import com.project.growfit.domain.auth.dto.response.ParentLoginResponseDto;
import com.project.growfit.global.auth.cookie.CookieService;
import com.project.growfit.global.auth.jwt.JwtProvider;
import com.project.growfit.global.redis.entity.TokenRedis;
Expand Down
Loading