diff --git a/src/main/java/org/depromeet/sambad/moring/auth/application/AuthService.java b/src/main/java/org/depromeet/sambad/moring/auth/application/AuthService.java index f152e61f..b08b6ba2 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/application/AuthService.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/application/AuthService.java @@ -63,7 +63,7 @@ private LoginResult generateLoginResult(User user, boolean firstLogin) { refreshTokenEntity.rotate(refreshToken); refreshTokenRepository.save(refreshTokenEntity); - return new LoginResult(accessToken, refreshToken, firstLogin, user.getId()); + return new LoginResult(accessToken, refreshToken, firstLogin, !user.getOnboardingCompleted(), user.getId()); } private User saveNewUser(AuthAttributes attributes) { diff --git a/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java b/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java index 12cf34ee..0f527f6b 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java @@ -72,6 +72,10 @@ private String determineRedirectUrl(LoginResult result, String redirectCookie) { return redirectCookie; } + if (result.isNotCompletedOnboarding()) { + return securityProperties.onboardingRedirectUrl(); + } + if (meetingMemberService.isNotEnterAnyMeeting(result.userId())) { return result.isNewUser() ? securityProperties.newUserRedirectUrl() + "?newUser=true" diff --git a/src/main/java/org/depromeet/sambad/moring/auth/application/RefreshTokenService.java b/src/main/java/org/depromeet/sambad/moring/auth/application/RefreshTokenService.java index eea9c2a1..c8b1372f 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/application/RefreshTokenService.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/application/RefreshTokenService.java @@ -40,7 +40,8 @@ private LoginResult getReissuedTokenResult(HttpServletResponse response, Refresh String reissuedAccessToken = tokenGenerator.generateAccessToken(userId); String rotatedRefreshToken = this.rotate(savedRefreshToken); - LoginResult loginResult = new LoginResult(reissuedAccessToken, rotatedRefreshToken, false, userId); + LoginResult loginResult = new LoginResult( + reissuedAccessToken, rotatedRefreshToken, false, false, userId); tokenInjector.injectTokensToCookie(loginResult, response); return loginResult; diff --git a/src/main/java/org/depromeet/sambad/moring/auth/domain/LoginResult.java b/src/main/java/org/depromeet/sambad/moring/auth/domain/LoginResult.java index 1f246a94..a9efb8e9 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/domain/LoginResult.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/domain/LoginResult.java @@ -4,6 +4,7 @@ public record LoginResult( String accessToken, String refreshToken, boolean isNewUser, + boolean isNotCompletedOnboarding, Long userId ) { } diff --git a/src/main/java/org/depromeet/sambad/moring/auth/infrastructure/SecurityProperties.java b/src/main/java/org/depromeet/sambad/moring/auth/infrastructure/SecurityProperties.java index f37f9b8c..11a0f023 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/infrastructure/SecurityProperties.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/infrastructure/SecurityProperties.java @@ -8,6 +8,7 @@ public record SecurityProperties( String loginUrl, String redirectUrl, String newUserRedirectUrl, + String onboardingRedirectUrl, @NestedConfigurationProperty Cookie cookie ) { diff --git a/src/main/java/org/depromeet/sambad/moring/common/domain/BaseTimeEntity.java b/src/main/java/org/depromeet/sambad/moring/common/domain/BaseTimeEntity.java index b44a9f49..8c9a62a4 100644 --- a/src/main/java/org/depromeet/sambad/moring/common/domain/BaseTimeEntity.java +++ b/src/main/java/org/depromeet/sambad/moring/common/domain/BaseTimeEntity.java @@ -1,6 +1,7 @@ package org.depromeet.sambad.moring.common.domain; import java.time.LocalDateTime; +import java.time.ZoneId; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -23,5 +24,13 @@ public abstract class BaseTimeEntity { @UpdateTimestamp @Column(nullable = false) protected LocalDateTime updatedAt; + + public Long getCreatedAtWithEpochMilli() { + return this.createdAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + public Long getUpdatedAtWithEpochMilli() { + return this.updatedAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } } diff --git a/src/main/java/org/depromeet/sambad/moring/user/application/UserService.java b/src/main/java/org/depromeet/sambad/moring/user/application/UserService.java index aea3df30..64a90fb2 100644 --- a/src/main/java/org/depromeet/sambad/moring/user/application/UserService.java +++ b/src/main/java/org/depromeet/sambad/moring/user/application/UserService.java @@ -1,13 +1,17 @@ package org.depromeet.sambad.moring.user.application; +import org.depromeet.sambad.moring.user.domain.User; import org.depromeet.sambad.moring.user.domain.UserRepository; import org.depromeet.sambad.moring.user.presentation.exception.NotFoundUserException; +import org.depromeet.sambad.moring.user.presentation.response.OnboardingResponse; import org.depromeet.sambad.moring.user.presentation.response.UserResponse; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor +@Transactional(readOnly = true) @Service public class UserService { @@ -18,4 +22,14 @@ public UserResponse findByUserId(Long userId) { .map(UserResponse::from) .orElseThrow(NotFoundUserException::new); } + + @Transactional + public OnboardingResponse completeOnboarding(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(NotFoundUserException::new); + + user.completeOnboarding(); + + return OnboardingResponse.from(user); + } } diff --git a/src/main/java/org/depromeet/sambad/moring/user/domain/User.java b/src/main/java/org/depromeet/sambad/moring/user/domain/User.java index ec9a9524..ad58744f 100644 --- a/src/main/java/org/depromeet/sambad/moring/user/domain/User.java +++ b/src/main/java/org/depromeet/sambad/moring/user/domain/User.java @@ -1,7 +1,5 @@ package org.depromeet.sambad.moring.user.domain; -import java.time.LocalDateTime; -import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -50,22 +48,29 @@ public class User extends BaseTimeEntity { private String externalId; + @Column(columnDefinition = "TINYINT") + private Boolean onboardingCompleted; + @OneToMany(mappedBy = "user") private List meetingMember = new ArrayList<>(); - private User(FileEntity imageFile, String name, String email, LoginProvider loginProvider, String externalId) { + private User( + FileEntity imageFile, String name, String email, LoginProvider loginProvider, String externalId, + boolean onboardingCompleted + ) { this.profileImageFile = imageFile; this.name = name; this.email = email; this.loginProvider = loginProvider; this.externalId = externalId; + this.onboardingCompleted = onboardingCompleted; } public static User from( FileEntity imageFile, AuthAttributes authAttributes ) { return new User(imageFile, authAttributes.getName(), authAttributes.getEmail(), authAttributes.getProvider(), - authAttributes.getExternalId()); + authAttributes.getExternalId(), false); } public String getProfileImageFileUrl() { @@ -81,7 +86,15 @@ public boolean hasDifferentProviderWithEmail(String email, String externalId) { return Objects.equals(this.email, email) && !Objects.equals(this.externalId, externalId); } - public Long toEpochMilli(LocalDateTime localDateTime) { - return getCreatedAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + public void updateProfileImage(FileEntity fileEntity) { + this.profileImageFile = fileEntity; + } + + public void completeOnboarding() { + this.onboardingCompleted = true; + } + + public boolean isNotEnteredAnyMeeting() { + return meetingMember.isEmpty(); } } diff --git a/src/main/java/org/depromeet/sambad/moring/user/presentation/UserController.java b/src/main/java/org/depromeet/sambad/moring/user/presentation/UserController.java index bf837594..5186a968 100644 --- a/src/main/java/org/depromeet/sambad/moring/user/presentation/UserController.java +++ b/src/main/java/org/depromeet/sambad/moring/user/presentation/UserController.java @@ -1,10 +1,13 @@ package org.depromeet.sambad.moring.user.presentation; import org.depromeet.sambad.moring.user.application.UserService; +import org.depromeet.sambad.moring.user.domain.User; import org.depromeet.sambad.moring.user.presentation.resolver.UserId; +import org.depromeet.sambad.moring.user.presentation.response.OnboardingResponse; import org.depromeet.sambad.moring.user.presentation.response.UserResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -31,4 +34,15 @@ public ResponseEntity getUser(@UserId Long userId) { UserResponse response = userService.findByUserId(userId); return ResponseEntity.ok(response); } + + @Operation( + summary = "온보딩 완료", + description = "온보딩을 완료함으로써, 더 이상 온보딩 페이지로 이동하지 않도록 수정합니다." + ) + @ApiResponse(responseCode = "200", description = "성공") + @PatchMapping("/onboarding/complete") + public ResponseEntity completeOnboarding(@UserId Long userId) { + OnboardingResponse response = userService.completeOnboarding(userId); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/org/depromeet/sambad/moring/user/presentation/response/OnboardingResponse.java b/src/main/java/org/depromeet/sambad/moring/user/presentation/response/OnboardingResponse.java new file mode 100644 index 00000000..02eca150 --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/user/presentation/response/OnboardingResponse.java @@ -0,0 +1,17 @@ +package org.depromeet.sambad.moring.user.presentation.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*; + +import org.depromeet.sambad.moring.user.domain.User; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record OnboardingResponse( + @Schema(description = "모임에 하나도 가입되어 있지 않다면 true", example = "false", requiredMode = REQUIRED) + boolean isNotEnteredAnyMeeting +) { + + public static OnboardingResponse from(User user) { + return new OnboardingResponse(user.isNotEnteredAnyMeeting()); + } +} diff --git a/src/main/java/org/depromeet/sambad/moring/user/presentation/response/UserResponse.java b/src/main/java/org/depromeet/sambad/moring/user/presentation/response/UserResponse.java index 5a027638..7d1d6f7a 100644 --- a/src/main/java/org/depromeet/sambad/moring/user/presentation/response/UserResponse.java +++ b/src/main/java/org/depromeet/sambad/moring/user/presentation/response/UserResponse.java @@ -30,8 +30,8 @@ public static UserResponse from(User user) { user.getName(), user.getEmail(), user.getProfileImageFileUrl(), - user.toEpochMilli(user.getCreatedAt()), - user.toEpochMilli(user.getUpdatedAt()) + user.getCreatedAtWithEpochMilli(), + user.getUpdatedAtWithEpochMilli() ); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e5cc754e..059e33e2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -34,6 +34,7 @@ spring: login-url: ${LOGIN_URL:/login} redirect-url: ${REDIRECT_URL:/} new-user-redirect-url: ${NEW_USER_REDIRECT_URL:/} + onboarding-redirect-url: ${ONBOARDING_REDIRECT_URL:/onboarding} cookie: domain: ${COOKIE_DOMAIN:moring.one} secure: ${COOKIE_SECURE:true}