Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ResponseDto<GoogleLoginResponseV3>> 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<SignUpResponse> signUpV3(
@Valid @RequestBody SignUpRequestV3 request
) {
SignUpResponse response = authUsecase.signUpV3(request.toCommand());
return ResponseDto.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -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<UserJobInfoResponse> updateUserJob(
@Parameter(hidden = true) @CurrentUser User user,
@Valid @RequestBody UpdateUserJobRequest request
) {
UserJobInfoResponse response = userManagementPort.updateUserJobInfo(user, request.toCommand());
return ResponseDto.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@
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;
import com.pinback.application.user.port.out.UserUpdateServicePort;
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;
Expand Down Expand Up @@ -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<GoogleLoginResponseV3> getInfoAndTokenV3(String email, String pictureUrl, String name) {
return userGetServicePort.findUserByEmail(email)
.flatMap(existingUser -> {

Mono<User> 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<String> 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<User> applyMissingUserInfo(User existingUser, String pictureUrl, String name) {
// 1. 이름 업데이트
boolean nameUpdated = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pinback.application.user.dto.command;

public record UpdateUserJobCommand(
String job
) {
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,4 +19,6 @@ public interface UserManagementPort {
UserProfileInfoResponse getUserProfileInfo(User user);

UserGoogleProfileResponse getUserGoogleProfile(User user);

UserJobInfoResponse updateUserJobInfo(User user, UpdateUserJobCommand command);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -13,4 +14,6 @@ public interface UserUpdateServicePort {
Mono<User> updateUser(User user);

void updateProfileImage(UUID userId, String imageProfile);

void updateJob(UUID userId, Job job);
}
Loading