diff --git a/src/main/java/konkuk/chacall/domain/user/application/UserService.java b/src/main/java/konkuk/chacall/domain/user/application/UserService.java new file mode 100644 index 00000000..20e5b42f --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/user/application/UserService.java @@ -0,0 +1,33 @@ +package konkuk.chacall.domain.user.application; + +import konkuk.chacall.domain.user.domain.model.User; +import konkuk.chacall.domain.user.domain.repository.UserRepository; +import konkuk.chacall.domain.user.presentation.dto.request.UpdateUserInfoRequest; +import konkuk.chacall.domain.user.presentation.dto.response.UserResponse; +import konkuk.chacall.global.common.exception.EntityNotFoundException; +import konkuk.chacall.global.common.exception.code.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserService { + + private final UserRepository userRepository; + + public UserResponse getUserInfo(Long userId) { + return userRepository.findById(userId) + .map(UserResponse::from) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.USER_NOT_FOUND)); + } + + @Transactional + public void updateUserInfo(Long userId, UpdateUserInfoRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.USER_NOT_FOUND)); + + user.update(request.name(), request.profileImageUrl(), request.email(), request.gender(), request.termAgreed()); + } +} diff --git a/src/main/java/konkuk/chacall/domain/user/domain/model/Gender.java b/src/main/java/konkuk/chacall/domain/user/domain/model/Gender.java index 6bf5df32..da38c36a 100644 --- a/src/main/java/konkuk/chacall/domain/user/domain/model/Gender.java +++ b/src/main/java/konkuk/chacall/domain/user/domain/model/Gender.java @@ -1,7 +1,11 @@ package konkuk.chacall.domain.user.domain.model; +import konkuk.chacall.global.common.exception.DomainRuleException; +import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.Getter; +import java.util.Arrays; + @Getter public enum Gender { M("남성"), F("여성"); @@ -11,4 +15,16 @@ public enum Gender { Gender(String value) { this.value = value; } + + public static Gender from(String value) { + return Arrays.stream(Gender.values()) + .filter(genderVal -> genderVal.getValue().equals(value.trim())) + .findFirst() + .orElseThrow( + () -> new DomainRuleException(ErrorCode.USER_GENDER_MISMATCH, + new IllegalArgumentException( + String.format("존재하지 않는 성별입니다. value: %s", value) + )) + ); + } } diff --git a/src/main/java/konkuk/chacall/domain/user/domain/model/User.java b/src/main/java/konkuk/chacall/domain/user/domain/model/User.java index 215bb448..d31ff904 100644 --- a/src/main/java/konkuk/chacall/domain/user/domain/model/User.java +++ b/src/main/java/konkuk/chacall/domain/user/domain/model/User.java @@ -37,13 +37,26 @@ public class User extends BaseEntity { @Column(length = 15, nullable = false) private Role role; + // 약관 동의 여부 컬럼 + @Column(nullable = false) + private boolean termsAgreed; + public static User createNewUser(String name, String profileImageUrl, String kakaoId, String email) { return User.builder() .name(name) .profileImageUrl(profileImageUrl) .kakaoId(kakaoId) .email(email) + .termsAgreed(false) .role(Role.NON_SELECTED) .build(); } + + public void update(String name, String profileImageUrl, String email, String genderStr, boolean termsAgreed) { + this.name = name; + this.profileImageUrl = profileImageUrl; + this.email = email; + this.gender = Gender.from(genderStr); + this.termsAgreed = termsAgreed; + } } diff --git a/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java b/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java new file mode 100644 index 00000000..d433de97 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java @@ -0,0 +1,45 @@ +package konkuk.chacall.domain.user.presentation; + +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 konkuk.chacall.domain.user.application.UserService; +import konkuk.chacall.domain.user.presentation.dto.request.UpdateUserInfoRequest; +import konkuk.chacall.domain.user.presentation.dto.response.UserResponse; +import konkuk.chacall.global.common.annotation.ExceptionDescription; +import konkuk.chacall.global.common.annotation.UserId; +import konkuk.chacall.global.common.dto.BaseResponse; +import konkuk.chacall.global.common.swagger.SwaggerResponseDescription; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "User API", description = "전체 사용자(마이페이지) 관련 API") +@RestController +@RequestMapping("/users") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @Operation(summary = "[마이페이지] 회원 정보 조회", description = "사용자(고객)의 정보를 조회합니다. (사장님, 일반유저 무관)") + @ExceptionDescription(SwaggerResponseDescription.GET_USER_INFO) + @GetMapping("/me") + public BaseResponse getUserInfo( + @Parameter(hidden = true) @UserId final Long userId + ) { + return BaseResponse.ok(userService.getUserInfo(userId)); + } + + @Operation(summary = "[마이페이지] 회원 정보 수정", description = "사용자(고객)의 정보를 수정합니다. (사장님, 일반유저 무관)") + @ExceptionDescription(SwaggerResponseDescription.UPDATE_USER_INFO) + @PutMapping("/me") + public BaseResponse updateUserInfo( + @RequestBody @Valid final UpdateUserInfoRequest request, + @Parameter(hidden = true) @UserId final Long userId + ) { + userService.updateUserInfo(userId, request); + + return BaseResponse.ok(null); + } +} diff --git a/src/main/java/konkuk/chacall/domain/user/presentation/dto/request/UpdateUserInfoRequest.java b/src/main/java/konkuk/chacall/domain/user/presentation/dto/request/UpdateUserInfoRequest.java new file mode 100644 index 00000000..61a6bf92 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/user/presentation/dto/request/UpdateUserInfoRequest.java @@ -0,0 +1,32 @@ +package konkuk.chacall.domain.user.presentation.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "사용자 정보 수정 요청 DTO") +public record UpdateUserInfoRequest( + + @Schema(description = "프로필 이미지 URL", example = "https://example.com/profile.jpg") + @NotBlank(message = "프로필 이미지 URL은 비어 있을 수 없습니다.") + String profileImageUrl, + + @Schema(description = "사용자 이름", example = "홍길동") + @NotBlank(message = "이름은 비어 있을 수 없습니다.") + String name, + + @Schema(description = "사용자 이메일", example = "chacall@kokuk.ac.kr") + @NotBlank(message = "이메일은 비어 있을 수 없습니다.") + @Email(message = "올바른 이메일 형식이 아닙니다.") + String email, + + @Schema(description = "사용자 성별", example = "남성") + @NotBlank(message = "성별은 비어 있을 수 없습니다.") + String gender, + + @Schema(description = "약관 동의 여부", example = "true") + @NotNull(message = "약관 동의 여부는 비어 있을 수 없습니다.") + Boolean termAgreed +) { +} diff --git a/src/main/java/konkuk/chacall/domain/user/presentation/dto/response/UserResponse.java b/src/main/java/konkuk/chacall/domain/user/presentation/dto/response/UserResponse.java new file mode 100644 index 00000000..0fe69c48 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/user/presentation/dto/response/UserResponse.java @@ -0,0 +1,21 @@ +package konkuk.chacall.domain.user.presentation.dto.response; + +import konkuk.chacall.domain.user.domain.model.User; + +public record UserResponse( + String profileImageUrl, + String name, + String email, + String gender, + boolean termAgreed +) { + public static UserResponse from(User user) { + return new UserResponse( + user.getProfileImageUrl(), + user.getName(), + user.getEmail(), + user.getGender() == null ? null : user.getGender().getValue(), + user.isTermsAgreed() + ); + } +} diff --git a/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java b/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java index 550ef234..11651c93 100644 --- a/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java @@ -33,6 +33,7 @@ public enum ErrorCode implements ResponseCode { USER_NOT_FOUND(HttpStatus.NOT_FOUND, 60001, "사용자를 찾을 수 없습니다."), USER_ALREADY_EXISTS(HttpStatus.CONFLICT, 60002, "이미 존재하는 사용자입니다."), USER_NICKNAME_DUPLICATION(HttpStatus.CONFLICT, 60003, "이미 존재하는 닉네임입니다."), + USER_GENDER_MISMATCH(HttpStatus.BAD_REQUEST, 60004, "일치하는 성별이 없습니다."), /** * BankAccount diff --git a/src/main/java/konkuk/chacall/global/common/security/oauth2/auth/AuthController.java b/src/main/java/konkuk/chacall/global/common/security/oauth2/auth/AuthController.java index c80290e5..a3b67aa2 100644 --- a/src/main/java/konkuk/chacall/global/common/security/oauth2/auth/AuthController.java +++ b/src/main/java/konkuk/chacall/global/common/security/oauth2/auth/AuthController.java @@ -35,7 +35,7 @@ public class AuthController { */ @Operation(summary = "JWT Access Token 발급", description = "loginTokenKey를 통해 JWT Access Token을 발급받습니다." + " 카카오 소셜 로그인 직후 리다이렉트 url의 쿼리 파라미터에서 loginTokenKey를 꺼내서 요청해주세요.") - @ExceptionDescription(SwaggerResponseDescription.LOGOUT) + @ExceptionDescription(SwaggerResponseDescription.ISSUE_TOKEN) @PostMapping("/token") public BaseResponse getToken( @RequestBody @Valid AuthTokenRequest request diff --git a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java index bd2b1231..99dc3b00 100644 --- a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java @@ -19,7 +19,14 @@ public enum SwaggerResponseDescription { ))), ISSUE_TOKEN(new LinkedHashSet<>(Set.of( - AUTH_INVALID_LOGIN_TOKEN_KEY, + AUTH_INVALID_LOGIN_TOKEN_KEY + ))), + + // User + GET_USER_INFO(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND + ))), + UPDATE_USER_INFO(new LinkedHashSet<>(Set.of( USER_NOT_FOUND ))),