From a3fa7020f0a2147203df6674a031094845cac1f5 Mon Sep 17 00:00:00 2001 From: ose0221 Date: Fri, 13 Feb 2026 01:23:46 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EC=A7=81=EB=AC=B4=20ENUM=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/pinback/domain/user/enums/Job.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 domain/src/main/java/com/pinback/domain/user/enums/Job.java diff --git a/domain/src/main/java/com/pinback/domain/user/enums/Job.java b/domain/src/main/java/com/pinback/domain/user/enums/Job.java new file mode 100644 index 0000000..4f2f818 --- /dev/null +++ b/domain/src/main/java/com/pinback/domain/user/enums/Job.java @@ -0,0 +1,28 @@ +package com.pinback.domain.user.enums; + +import com.pinback.domain.user.exception.JobNotFoundException; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public enum Job { + PLAN("PLAN", "기획자"), + DESIGN("DESIGN", "디자이너"), + FRONTEND("FRONTEND", "프론트엔드 개발자"), + BACKEND("BACKEND", "백엔드 개발자"); + + private final String key; + private final String value; + + public static Job from(String value) { + for (Job job : Job.values()) { + if (job.getValue().equals(value)) { + return job; + } + } + throw new JobNotFoundException(); + } +} From dcb0dd753b4de8c34206f98b1dbd45c22c1cd6c7 Mon Sep 17 00:00:00 2001 From: ose0221 Date: Fri, 13 Feb 2026 01:25:10 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=A7=81=EB=AC=B4=20ENUM=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/exception/JobNotFoundException.java | 10 ++++++++++ .../com/pinback/shared/constant/ExceptionCode.java | 1 + 2 files changed, 11 insertions(+) create mode 100644 domain/src/main/java/com/pinback/domain/user/exception/JobNotFoundException.java diff --git a/domain/src/main/java/com/pinback/domain/user/exception/JobNotFoundException.java b/domain/src/main/java/com/pinback/domain/user/exception/JobNotFoundException.java new file mode 100644 index 0000000..7b72259 --- /dev/null +++ b/domain/src/main/java/com/pinback/domain/user/exception/JobNotFoundException.java @@ -0,0 +1,10 @@ +package com.pinback.domain.user.exception; + +import com.pinback.shared.constant.ExceptionCode; +import com.pinback.shared.exception.ApplicationException; + +public class JobNotFoundException extends ApplicationException { + public JobNotFoundException() { + super(ExceptionCode.JOB_NOT_FOUND); + } +} diff --git a/shared/src/main/java/com/pinback/shared/constant/ExceptionCode.java b/shared/src/main/java/com/pinback/shared/constant/ExceptionCode.java index 9303c14..b65d1b7 100644 --- a/shared/src/main/java/com/pinback/shared/constant/ExceptionCode.java +++ b/shared/src/main/java/com/pinback/shared/constant/ExceptionCode.java @@ -30,6 +30,7 @@ public enum ExceptionCode { CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "c40402", "카테고리가 존재하지 않습니다."), ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "c40403", "아티클이 존재하지 않습니다."), SUBSCRIPTION_NOT_FOUND(HttpStatus.NOT_FOUND, "c40404", "구독정보가 존재하지 않습니다."), + JOB_NOT_FOUND(HttpStatus.NOT_FOUND, "c40405", "존재하지 않는 직무입니다."), //405 METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "c40500", "잘못된 HTTP method 요청입니다."), From a02aac8e0199cd181028ea667a804cd87263f964 Mon Sep 17 00:00:00 2001 From: ose0221 Date: Fri, 13 Feb 2026 01:25:55 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20User=20=EC=A7=81=EB=AC=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/user/controller/UserControllerV3.java | 35 +++++++++++++++++++ .../dto/response/UserJobInfoResponse.java | 9 +++++ .../user/port/in/UserManagementPort.java | 4 +++ .../user/port/out/UserUpdateServicePort.java | 3 ++ .../user/usecase/UserManagementUsecase.java | 26 +++++++++++++- .../com/pinback/domain/user/entity/User.java | 15 ++++++++ .../user/repository/UserRepository.java | 5 +++ .../user/service/UserUpdateService.java | 6 ++++ 8 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/com/pinback/api/user/controller/UserControllerV3.java create mode 100644 application/src/main/java/com/pinback/application/user/dto/response/UserJobInfoResponse.java diff --git a/api/src/main/java/com/pinback/api/user/controller/UserControllerV3.java b/api/src/main/java/com/pinback/api/user/controller/UserControllerV3.java new file mode 100644 index 0000000..66c494f --- /dev/null +++ b/api/src/main/java/com/pinback/api/user/controller/UserControllerV3.java @@ -0,0 +1,35 @@ +package com.pinback.api.user.controller; + +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.pinback.api.user.dto.request.UpdateUserJobRequest; +import com.pinback.application.user.dto.response.UserJobInfoResponse; +import com.pinback.application.user.port.in.UserManagementPort; +import com.pinback.domain.user.entity.User; +import com.pinback.shared.annotation.CurrentUser; +import com.pinback.shared.dto.ResponseDto; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/api/v3/users") +@RequiredArgsConstructor +@Tag(name = "User V3", description = "사용자 관리 API V3") +public class UserControllerV3 { + private final UserManagementPort userManagementPort; + + @PatchMapping("/job") + public ResponseDto updateUserJob( + @Parameter(hidden = true) @CurrentUser User user, + @Valid @RequestBody UpdateUserJobRequest request + ) { + UserJobInfoResponse response = userManagementPort.updateUserJobInfo(user, request.toCommand()); + return ResponseDto.ok(response); + } +} diff --git a/application/src/main/java/com/pinback/application/user/dto/response/UserJobInfoResponse.java b/application/src/main/java/com/pinback/application/user/dto/response/UserJobInfoResponse.java new file mode 100644 index 0000000..d4ebda4 --- /dev/null +++ b/application/src/main/java/com/pinback/application/user/dto/response/UserJobInfoResponse.java @@ -0,0 +1,9 @@ +package com.pinback.application.user.dto.response; + +public record UserJobInfoResponse( + String job +) { + public static UserJobInfoResponse of(String job) { + return new UserJobInfoResponse(job); + } +} diff --git a/application/src/main/java/com/pinback/application/user/port/in/UserManagementPort.java b/application/src/main/java/com/pinback/application/user/port/in/UserManagementPort.java index 3c27cd2..8136837 100644 --- a/application/src/main/java/com/pinback/application/user/port/in/UserManagementPort.java +++ b/application/src/main/java/com/pinback/application/user/port/in/UserManagementPort.java @@ -2,8 +2,10 @@ import java.time.LocalDateTime; +import com.pinback.application.user.dto.command.UpdateUserJobCommand; import com.pinback.application.user.dto.response.UserGoogleProfileResponse; import com.pinback.application.user.dto.response.UserInfoResponse; +import com.pinback.application.user.dto.response.UserJobInfoResponse; import com.pinback.application.user.dto.response.UserProfileInfoResponse; import com.pinback.application.user.dto.response.UserRemindInfoResponse; import com.pinback.domain.user.entity.User; @@ -17,4 +19,6 @@ public interface UserManagementPort { UserProfileInfoResponse getUserProfileInfo(User user); UserGoogleProfileResponse getUserGoogleProfile(User user); + + UserJobInfoResponse updateUserJobInfo(User user, UpdateUserJobCommand command); } diff --git a/application/src/main/java/com/pinback/application/user/port/out/UserUpdateServicePort.java b/application/src/main/java/com/pinback/application/user/port/out/UserUpdateServicePort.java index 4199d29..aeaa352 100644 --- a/application/src/main/java/com/pinback/application/user/port/out/UserUpdateServicePort.java +++ b/application/src/main/java/com/pinback/application/user/port/out/UserUpdateServicePort.java @@ -4,6 +4,7 @@ import java.util.UUID; import com.pinback.domain.user.entity.User; +import com.pinback.domain.user.enums.Job; import reactor.core.publisher.Mono; @@ -13,4 +14,6 @@ public interface UserUpdateServicePort { Mono updateUser(User user); void updateProfileImage(UUID userId, String imageProfile); + + void updateJob(UUID userId, Job job); } diff --git a/application/src/main/java/com/pinback/application/user/usecase/UserManagementUsecase.java b/application/src/main/java/com/pinback/application/user/usecase/UserManagementUsecase.java index 0073180..863192f 100644 --- a/application/src/main/java/com/pinback/application/user/usecase/UserManagementUsecase.java +++ b/application/src/main/java/com/pinback/application/user/usecase/UserManagementUsecase.java @@ -10,24 +10,31 @@ import org.springframework.transaction.annotation.Transactional; import com.pinback.application.config.ProfileImageConfig; +import com.pinback.application.user.dto.command.UpdateUserJobCommand; import com.pinback.application.user.dto.response.UserGoogleProfileResponse; import com.pinback.application.user.dto.response.UserInfoResponse; +import com.pinback.application.user.dto.response.UserJobInfoResponse; import com.pinback.application.user.dto.response.UserProfileInfoResponse; import com.pinback.application.user.dto.response.UserRemindInfoResponse; import com.pinback.application.user.port.in.UserManagementPort; import com.pinback.application.user.port.out.AcornServicePort; +import com.pinback.application.user.port.out.UserGetServicePort; import com.pinback.domain.user.entity.User; +import com.pinback.domain.user.enums.Job; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor -@Transactional(readOnly = true) public class UserManagementUsecase implements UserManagementPort { private final AcornServicePort acornService; private final ProfileImageConfig profileImageConfig; + private final UserGetServicePort userGetServicePort; @Override + @Transactional(readOnly = true) public UserInfoResponse getUserInfo(User user, LocalDateTime now) { LocalTime userRemindDefault = user.getRemindDefault(); LocalDateTime remindDateTime = getRemindDateTime(now, userRemindDefault); @@ -38,6 +45,7 @@ public UserInfoResponse getUserInfo(User user, LocalDateTime now) { } @Override + @Transactional(readOnly = true) public UserRemindInfoResponse getUserRemindInfo(User user, LocalDateTime now) { LocalTime userRemindTime = user.getRemindDefault(); LocalDate userRemindDate = now.toLocalDate().plusDays(1L); @@ -46,6 +54,7 @@ public UserRemindInfoResponse getUserRemindInfo(User user, LocalDateTime now) { } @Override + @Transactional(readOnly = true) public UserProfileInfoResponse getUserProfileInfo(User user) { String name = user.getUsername(); String email = user.getEmail(); @@ -58,11 +67,25 @@ public UserProfileInfoResponse getUserProfileInfo(User user) { } @Override + @Transactional(readOnly = true) public UserGoogleProfileResponse getUserGoogleProfile(User user) { String googleProfile = user.getGoogleProfileImage(); return UserGoogleProfileResponse.of(googleProfile); } + @Override + @Transactional + public UserJobInfoResponse updateUserJobInfo(User user, UpdateUserJobCommand command) { + User getUser = userGetServicePort.findById(user.getId()); + + Job job = Job.from(command.job()); + getUser.updateJob(job); + log.info("user: {}, job: {}, user.job: {}", user.getId(), job, getUser.getJob()); + + String updatedJob = getUser.getJob().getValue(); + return UserJobInfoResponse.of(updatedJob); + } + private LocalDateTime getRemindDateTime(LocalDateTime now, LocalTime remindDefault) { LocalDateTime remindDate = now.plusDays(1L); @@ -70,4 +93,5 @@ private LocalDateTime getRemindDateTime(LocalDateTime now, LocalTime remindDefau remindDate.getDayOfMonth(), remindDefault.getHour(), remindDefault.getMinute()); } + } diff --git a/domain/src/main/java/com/pinback/domain/user/entity/User.java b/domain/src/main/java/com/pinback/domain/user/entity/User.java index 9549fbc..41d4d66 100644 --- a/domain/src/main/java/com/pinback/domain/user/entity/User.java +++ b/domain/src/main/java/com/pinback/domain/user/entity/User.java @@ -5,9 +5,12 @@ import java.util.UUID; import com.pinback.domain.common.BaseEntity; +import com.pinback.domain.user.enums.Job; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -51,6 +54,10 @@ public class User extends BaseEntity { @Column(name = "profile_image") private String profileImage; + @Enumerated(EnumType.STRING) + @Column(name = "job") + private Job job; + public static User create(String email, LocalTime remindDefault) { return User.builder() .email(email) @@ -91,4 +98,12 @@ public void updateName(String name) { public void updateProfileImage(String profileImage) { this.profileImage = profileImage; } + + public void updateJob(Job job) { + this.job = job; + } + + public boolean hasJob() { + return this.job != null; + } } diff --git a/infrastructure/src/main/java/com/pinback/infrastructure/user/repository/UserRepository.java b/infrastructure/src/main/java/com/pinback/infrastructure/user/repository/UserRepository.java index 506d334..6a0892f 100644 --- a/infrastructure/src/main/java/com/pinback/infrastructure/user/repository/UserRepository.java +++ b/infrastructure/src/main/java/com/pinback/infrastructure/user/repository/UserRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.repository.query.Param; import com.pinback.domain.user.entity.User; +import com.pinback.domain.user.enums.Job; public interface UserRepository extends JpaRepository { @@ -24,4 +25,8 @@ public interface UserRepository extends JpaRepository { @Modifying @Query("UPDATE User u SET u.profileImage = :imageProfile WHERE u.id = :userId") void updateProfileImage(@Param("userId") UUID userId, @Param("imageProfile") String imageProfile); + + @Modifying + @Query("UPDATE User u SET u.job = :job WHERE u.id = :userId") + void updateJob(@Param("userId") UUID userId, @Param("job") Job job); } diff --git a/infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserUpdateService.java b/infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserUpdateService.java index 31571c0..141248c 100644 --- a/infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserUpdateService.java +++ b/infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserUpdateService.java @@ -8,6 +8,7 @@ import com.pinback.application.user.port.out.UserUpdateServicePort; import com.pinback.domain.user.entity.User; +import com.pinback.domain.user.enums.Job; import com.pinback.infrastructure.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -36,4 +37,9 @@ public Mono updateUser(User user) { public void updateProfileImage(UUID userId, String imageProfile) { userRepository.updateProfileImage(userId, imageProfile); } + + @Override + public void updateJob(UUID userId, Job job) { + userRepository.updateJob(userId, job); + } } From 5d10c9d849dafaf13b7dfcbf5616183633df178e Mon Sep 17 00:00:00 2001 From: ose0221 Date: Fri, 13 Feb 2026 01:27:03 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20GoogleLoginController=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{GoogleLonginController.java => GoogleLoginController.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename api/src/main/java/com/pinback/api/google/controller/{GoogleLonginController.java => GoogleLoginController.java} (98%) diff --git a/api/src/main/java/com/pinback/api/google/controller/GoogleLonginController.java b/api/src/main/java/com/pinback/api/google/controller/GoogleLoginController.java similarity index 98% rename from api/src/main/java/com/pinback/api/google/controller/GoogleLonginController.java rename to api/src/main/java/com/pinback/api/google/controller/GoogleLoginController.java index 5865a04..7100746 100644 --- a/api/src/main/java/com/pinback/api/google/controller/GoogleLonginController.java +++ b/api/src/main/java/com/pinback/api/google/controller/GoogleLoginController.java @@ -26,7 +26,7 @@ @RequestMapping("/api/v2/auth") @RequiredArgsConstructor @Tag(name = "Google", description = "구글 소셜 로그인 API") -public class GoogleLonginController { +public class GoogleLoginController { private final GoogleUsecase googleUsecase; private final AuthUsecase authUsecase; From 664a1cc402f1579183797325f7747da017db5bf8 Mon Sep 17 00:00:00 2001 From: ose0221 Date: Fri, 13 Feb 2026 01:28:41 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=EC=A7=81?= =?UTF-8?q?=EB=AC=B4=20=EC=A0=95=EB=B3=B4=20dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/user/dto/request/UpdateUserJobRequest.java | 11 +++++++++++ .../user/dto/command/UpdateUserJobCommand.java | 6 ++++++ 2 files changed, 17 insertions(+) create mode 100644 api/src/main/java/com/pinback/api/user/dto/request/UpdateUserJobRequest.java create mode 100644 application/src/main/java/com/pinback/application/user/dto/command/UpdateUserJobCommand.java diff --git a/api/src/main/java/com/pinback/api/user/dto/request/UpdateUserJobRequest.java b/api/src/main/java/com/pinback/api/user/dto/request/UpdateUserJobRequest.java new file mode 100644 index 0000000..90d017d --- /dev/null +++ b/api/src/main/java/com/pinback/api/user/dto/request/UpdateUserJobRequest.java @@ -0,0 +1,11 @@ +package com.pinback.api.user.dto.request; + +import com.pinback.application.user.dto.command.UpdateUserJobCommand; + +public record UpdateUserJobRequest( + String job +) { + public UpdateUserJobCommand toCommand() { + return new UpdateUserJobCommand(job); + } +} diff --git a/application/src/main/java/com/pinback/application/user/dto/command/UpdateUserJobCommand.java b/application/src/main/java/com/pinback/application/user/dto/command/UpdateUserJobCommand.java new file mode 100644 index 0000000..aa77a3c --- /dev/null +++ b/application/src/main/java/com/pinback/application/user/dto/command/UpdateUserJobCommand.java @@ -0,0 +1,6 @@ +package com.pinback.application.user.dto.command; + +public record UpdateUserJobCommand( + String job +) { +} From dd8b598b2e4ff3e2e28d32bc6f60450a59a5a415 Mon Sep 17 00:00:00 2001 From: ose0221 Date: Fri, 13 Feb 2026 01:29:07 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95=20dto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/category/dto/request/UpdateCategoryRequestV3.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/main/java/com/pinback/api/category/dto/request/UpdateCategoryRequestV3.java b/api/src/main/java/com/pinback/api/category/dto/request/UpdateCategoryRequestV3.java index f60c40d..90208bf 100644 --- a/api/src/main/java/com/pinback/api/category/dto/request/UpdateCategoryRequestV3.java +++ b/api/src/main/java/com/pinback/api/category/dto/request/UpdateCategoryRequestV3.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @Schema(description = "카테고리 수정 요청 V3") @@ -13,6 +14,7 @@ public record UpdateCategoryRequestV3( @Size(max = 50, message = "카테고리 이름은 50자 이하로 입력해주세요") String categoryName, @Schema(description = "카테고리 공개여부", example = "false") + @NotNull(message = "공개 여부는 필수 항목입니다(true/false)") Boolean isPublic ) { public UpdateCategoryCommandV3 toCommand() { From 4bd076344e512eec85b5ec7c2557a0d0ba52ea15 Mon Sep 17 00:00:00 2001 From: ose0221 Date: Fri, 13 Feb 2026 01:31:21 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B8=80=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=97=90=20=EC=A7=81=EB=AC=B4=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/auth/dto/request/SignUpRequestV3.java | 30 +++++++++ .../filter/JwtAuthenticationFilter.java | 4 +- .../api/config/security/SecurityConfig.java | 4 +- .../controller/GoogleLoginControllerV3.java | 54 +++++++++++++++ .../application/auth/dto/SignUpCommandV3.java | 11 ++++ .../application/auth/usecase/AuthUsecase.java | 66 +++++++++++++++++++ .../dto/response/GoogleLoginResponseV3.java | 19 ++++++ 7 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 api/src/main/java/com/pinback/api/auth/dto/request/SignUpRequestV3.java create mode 100644 api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java create mode 100644 application/src/main/java/com/pinback/application/auth/dto/SignUpCommandV3.java create mode 100644 application/src/main/java/com/pinback/application/google/dto/response/GoogleLoginResponseV3.java diff --git a/api/src/main/java/com/pinback/api/auth/dto/request/SignUpRequestV3.java b/api/src/main/java/com/pinback/api/auth/dto/request/SignUpRequestV3.java new file mode 100644 index 0000000..60cf382 --- /dev/null +++ b/api/src/main/java/com/pinback/api/auth/dto/request/SignUpRequestV3.java @@ -0,0 +1,30 @@ +package com.pinback.api.auth.dto.request; + +import java.time.LocalTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.pinback.application.auth.dto.SignUpCommandV3; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record SignUpRequestV3( + @NotBlank(message = "이메일은 비어있을 수 없습니다.") + String email, + + @Schema(description = "기본 알림 시간", example = "08:30", pattern = "HH:mm") + @JsonFormat(pattern = "HH:mm") + @NotNull(message = "리마인드 시간은 비어있을 수 없습니다.") + LocalTime remindDefault, + + @NotNull(message = "알림 정보는 비어있을 수 없습니다.") + String fcmToken, + + @NotBlank(message = "직무는 비어있을 수 없습니다.") + String job +) { + public SignUpCommandV3 toCommand() { + return new SignUpCommandV3(email, remindDefault, fcmToken, job); + } +} diff --git a/api/src/main/java/com/pinback/api/config/filter/JwtAuthenticationFilter.java b/api/src/main/java/com/pinback/api/config/filter/JwtAuthenticationFilter.java index e495529..09e898f 100644 --- a/api/src/main/java/com/pinback/api/config/filter/JwtAuthenticationFilter.java +++ b/api/src/main/java/com/pinback/api/config/filter/JwtAuthenticationFilter.java @@ -101,7 +101,9 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce path.startsWith("/oauth/callback") || path.startsWith("/login/google") || path.startsWith("/login/oauth2/code/google") || - path.startsWith("/api/v2/auth/signup") + path.startsWith("/api/v2/auth/signup") || + path.startsWith("/api/v3/auth/signup") || + path.startsWith("/api/v3/auth/google") ; } } diff --git a/api/src/main/java/com/pinback/api/config/security/SecurityConfig.java b/api/src/main/java/com/pinback/api/config/security/SecurityConfig.java index 74f9f40..20f37f5 100644 --- a/api/src/main/java/com/pinback/api/config/security/SecurityConfig.java +++ b/api/src/main/java/com/pinback/api/config/security/SecurityConfig.java @@ -54,7 +54,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers( "/api/v1/auth/token", "/api/v2/auth/google", - "/api/v2/auth/signup" + "/api/v2/auth/signup", + "/api/v3/auth/signup", + "/api/v3/auth/google" ).permitAll() .requestMatchers( diff --git a/api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java b/api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java new file mode 100644 index 0000000..1b69c7b --- /dev/null +++ b/api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java @@ -0,0 +1,54 @@ +package com.pinback.api.google.controller; + +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.pinback.api.auth.dto.request.SignUpRequestV3; +import com.pinback.api.google.dto.request.GoogleLoginRequest; +import com.pinback.application.auth.dto.SignUpResponse; +import com.pinback.application.auth.usecase.AuthUsecase; +import com.pinback.application.google.dto.response.GoogleLoginResponseV3; +import com.pinback.application.google.usecase.GoogleUsecase; +import com.pinback.shared.dto.ResponseDto; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/v3/auth") +@RequiredArgsConstructor +@Tag(name = "Google OAuth V3", description = "구글 소셜 로그인 API V3") +public class GoogleLoginControllerV3 { + private final GoogleUsecase googleUsecase; + private final AuthUsecase authUsecase; + + @Operation(summary = "구글 소셜 로그인 V3", description = "구글 소셜 로그인을 진행하며, 응답에 직무 선택 여부를 포함합니다.") + @PostMapping("/google") + public Mono> googleLogin( + @Valid @RequestBody GoogleLoginRequest request + ) { + return googleUsecase.getUserInfo(request.toCommand()) + .flatMap(googleResponse -> { + return authUsecase.getInfoAndTokenV3(googleResponse.email(), googleResponse.pictureUrl(), + googleResponse.name()) + .map(loginResponse -> { + return ResponseDto.ok(loginResponse); + }); + }); + } + + @Operation(summary = "신규 회원 온보딩 V3", description = "신규 회원의 기본 정보(직무 포함)를 등록합니다.") + @PatchMapping("/signup") + public ResponseDto signUpV3( + @Valid @RequestBody SignUpRequestV3 request + ) { + SignUpResponse response = authUsecase.signUpV3(request.toCommand()); + return ResponseDto.ok(response); + } +} diff --git a/application/src/main/java/com/pinback/application/auth/dto/SignUpCommandV3.java b/application/src/main/java/com/pinback/application/auth/dto/SignUpCommandV3.java new file mode 100644 index 0000000..b890681 --- /dev/null +++ b/application/src/main/java/com/pinback/application/auth/dto/SignUpCommandV3.java @@ -0,0 +1,11 @@ +package com.pinback.application.auth.dto; + +import java.time.LocalTime; + +public record SignUpCommandV3( + String email, + LocalTime remindDefault, + String fcmToken, + String job +) { +} diff --git a/application/src/main/java/com/pinback/application/auth/usecase/AuthUsecase.java b/application/src/main/java/com/pinback/application/auth/usecase/AuthUsecase.java index d28671a..624d5b0 100644 --- a/application/src/main/java/com/pinback/application/auth/usecase/AuthUsecase.java +++ b/application/src/main/java/com/pinback/application/auth/usecase/AuthUsecase.java @@ -6,11 +6,13 @@ import org.springframework.transaction.annotation.Transactional; import com.pinback.application.auth.dto.SignUpCommand; +import com.pinback.application.auth.dto.SignUpCommandV3; import com.pinback.application.auth.dto.SignUpResponse; import com.pinback.application.auth.dto.TokenResponse; import com.pinback.application.auth.service.JwtProvider; import com.pinback.application.config.ProfileImageConfig; import com.pinback.application.google.dto.response.GoogleLoginResponse; +import com.pinback.application.google.dto.response.GoogleLoginResponseV3; import com.pinback.application.notification.port.in.SavePushSubscriptionPort; import com.pinback.application.user.port.out.UserGetServicePort; import com.pinback.application.user.port.out.UserSaveServicePort; @@ -18,6 +20,7 @@ import com.pinback.application.user.port.out.UserValidateServicePort; import com.pinback.application.user.usecase.UserOAuthUsecase; import com.pinback.domain.user.entity.User; +import com.pinback.domain.user.enums.Job; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -119,6 +122,69 @@ public SignUpResponse signUpV2(SignUpCommand signUpCommand) { return SignUpResponse.from(accessToken); } + @Transactional + public SignUpResponse signUpV3(SignUpCommandV3 signUpCommand) { + User user = userGetServicePort.findByEmail(signUpCommand.email()); + String accessToken = jwtProvider.createAccessToken(user.getId()); + userUpdateServicePort.updateRemindDefault(user.getId(), signUpCommand.remindDefault()); + + savePushSubscriptionPort.savePushSubscription(user, signUpCommand.fcmToken()); + String profileImage = matchingProfileImage(signUpCommand.remindDefault()); + userUpdateServicePort.updateProfileImage(user.getId(), profileImage); + + Job job = Job.from(signUpCommand.job()); + userUpdateServicePort.updateJob(user.getId(), job); + + return SignUpResponse.from(accessToken); + } + + @Transactional + public Mono getInfoAndTokenV3(String email, String pictureUrl, String name) { + return userGetServicePort.findUserByEmail(email) + .flatMap(existingUser -> { + + Mono updateMono = applyMissingUserInfo(existingUser, pictureUrl, name); + return updateMono + .flatMap(updatedUser -> { + if (updatedUser.getRemindDefault() != null && updatedUser.getProfileImage() != null) { + log.info("기존 사용자 로그인 성공: User ID {}", updatedUser.getId()); + + //Access Token 발급 + String accessToken = jwtProvider.createAccessToken(updatedUser.getId()); + + return Mono.just(GoogleLoginResponseV3.loggedIn( + updatedUser.hasJob(), updatedUser.getId(), updatedUser.getEmail(), accessToken + )); + } else { + log.info("기존 사용자 - 온보딩 미완료 유저 처리: User ID {}", updatedUser.getId()); + + return Mono.just(GoogleLoginResponseV3.tempLogin( + updatedUser.getId(), updatedUser.getEmail() + )); + } + }); + }) + .switchIfEmpty(Mono.defer(() -> { + log.info("신규 유저 - 임시 유저 생성"); + User tempUser = User.createTempUser(email, name); + + return userSaveServicePort.saveUser(tempUser) + .flatMap(savedUser -> { + // 1. S3 이미지 저장 서비스 호출 + Mono s3UrlMono = userOAuthUsecase.saveProfileImage(savedUser.getId(), pictureUrl); + return s3UrlMono.flatMap(s3Url -> { + // 2. S3 URL로 유저 엔티티 업데이트 + savedUser.updateGoogleProfileImage(s3Url); + return userUpdateServicePort.updateUser(savedUser); + + }).then( + // 3. 최종 응답 반환 (이미지 저장 트랜잭션 완료 후) + Mono.just(GoogleLoginResponseV3.tempLogin(savedUser.getId(), savedUser.getEmail())) + ); + }); + })); + } + private Mono applyMissingUserInfo(User existingUser, String pictureUrl, String name) { // 1. 이름 업데이트 boolean nameUpdated = false; diff --git a/application/src/main/java/com/pinback/application/google/dto/response/GoogleLoginResponseV3.java b/application/src/main/java/com/pinback/application/google/dto/response/GoogleLoginResponseV3.java new file mode 100644 index 0000000..ab358ef --- /dev/null +++ b/application/src/main/java/com/pinback/application/google/dto/response/GoogleLoginResponseV3.java @@ -0,0 +1,19 @@ +package com.pinback.application.google.dto.response; + +import java.util.UUID; + +public record GoogleLoginResponseV3( + boolean isUser, + boolean hasJob, + UUID userId, + String email, + String accessToken +) { + public static GoogleLoginResponseV3 loggedIn(boolean hasJob, UUID userId, String email, String accessToken) { + return new GoogleLoginResponseV3(true, hasJob, userId, email, accessToken); + } + + public static GoogleLoginResponseV3 tempLogin(UUID userId, String email) { + return new GoogleLoginResponseV3(false, false, userId, email, null); + } +}