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
Expand Up @@ -143,6 +143,13 @@ public ResponseEntity<ApiResponse<AuthResponse>> emailSignup(
"ai_training": false,
"service_analytics": true,
"marketing_notification": false
},
"hair_profile": {
"hair_type": "STRAIGHT",
"hair_condition": "HEALTHY",
"hair_length": "SHORT",
"hair_thickness": "THIN",
"available_care_time_minutes": 15
}
}
""")))
Expand Down Expand Up @@ -181,6 +188,13 @@ public ResponseEntity<ApiResponse<AuthResponse>> socialSignup(
"ai_training": false,
"service_analytics": true,
"marketing_notification": false
},
"hair_profile": {
"hair_type": "STRAIGHT",
"hair_condition": "HEALTHY",
"hair_length": "SHORT",
"hair_thickness": "THIN",
"available_care_time_minutes": 15
}
}
""")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ public record EmailSignupRequest(
@NotNull @Valid
@Schema(description = "약관 동의. 5개 항목을 모두 보내야 한다",
requiredMode = Schema.RequiredMode.REQUIRED)
AgreementsRequest agreements
AgreementsRequest agreements,

@Valid
@Schema(description = "회원가입 온보딩 모발 프로필. 건너뛰는 경우 생략할 수 있으며, "
+ "전달할 때는 모발 유형·상태·길이·굵기를 모두 보내야 한다")
@JsonProperty("hair_profile") HairProfileRequest hairProfile
) {
public EmailSignupCommand toCommand(String policyVersion) {
return new EmailSignupCommand(
email.toLowerCase(), password, nickname, phoneNumber,
agreements.toDecisions(policyVersion));
agreements.toDecisions(policyVersion),
hairProfile == null ? null : hairProfile.toSignupCommand());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.heddy.domain.account.model.HairProfile.HairThickness;
import com.heddy.domain.account.model.HairProfile.HairType;
import com.heddy.domain.account.port.in.SaveHairProfileCommand;
import com.heddy.domain.account.port.in.SignupHairProfileCommand;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
Expand Down Expand Up @@ -41,4 +42,9 @@ public SaveHairProfileCommand toCommand(UUID userId) {
return new SaveHairProfileCommand(userId, hairType, hairCondition, hairLength,
hairThickness, availableCareTimeMinutes);
}

public SignupHairProfileCommand toSignupCommand() {
return new SignupHairProfileCommand(hairType, hairCondition, hairLength,
hairThickness, availableCareTimeMinutes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ public record SocialSignupRequest(
@NotNull @Valid
@Schema(description = "약관 동의. 5개 항목을 모두 보내야 한다",
requiredMode = Schema.RequiredMode.REQUIRED)
AgreementsRequest agreements
AgreementsRequest agreements,

@Valid
@Schema(description = "회원가입 온보딩 모발 프로필. 건너뛰는 경우 생략할 수 있으며, "
+ "전달할 때는 모발 유형·상태·길이·굵기를 모두 보내야 한다")
@JsonProperty("hair_profile") HairProfileRequest hairProfile
) {
public SocialSignupCommand toCommand(String policyVersion) {
return new SocialSignupCommand(
provider, providerToken, nickname, phoneNumber,
agreements.toDecisions(policyVersion));
agreements.toDecisions(policyVersion),
hairProfile == null ? null : hairProfile.toSignupCommand());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import com.heddy.domain.account.exception.AccountException;
import com.heddy.domain.account.model.Account;
import com.heddy.domain.account.model.ConsentSource;
import com.heddy.domain.account.model.HairProfile;
import com.heddy.domain.account.model.UserProfile;
import com.heddy.domain.account.port.in.AuthResult;
import com.heddy.domain.account.port.in.EmailSignupCommand;
import com.heddy.domain.account.port.in.EmailSignupUseCase;
import com.heddy.domain.account.port.out.AccountRepositoryPort;
import com.heddy.domain.account.port.out.ConsentHistoryRepositoryPort;
import com.heddy.domain.account.port.out.HairProfileRepositoryPort;
import com.heddy.domain.account.port.out.PasswordEncoderPort;
import com.heddy.domain.account.port.out.UserProfileRepositoryPort;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,6 +27,7 @@ public class EmailSignupService implements EmailSignupUseCase {

private final AccountRepositoryPort accountRepositoryPort;
private final UserProfileRepositoryPort userProfileRepositoryPort;
private final HairProfileRepositoryPort hairProfileRepositoryPort;
private final ConsentHistoryRepositoryPort consentHistoryRepositoryPort;
private final PasswordEncoderPort passwordEncoderPort;
private final SessionTokenService sessionTokenService;
Expand All @@ -45,6 +48,12 @@ public AuthResult signup(EmailSignupCommand command) {
userId, command.email(), passwordEncoderPort.encode(command.password())));
UserProfile profile = userProfileRepositoryPort.save(
UserProfile.signup(userId, command.nickname(), command.phone()));
if (command.hairProfile() != null) {
var hairProfile = command.hairProfile();
hairProfileRepositoryPort.save(HairProfile.create(userId, hairProfile.hairType(),
hairProfile.hairCondition(), hairProfile.hairLength(),
hairProfile.hairThickness(), hairProfile.availableCareTimeMinutes()));
}
consentHistoryRepositoryPort.append(
userId, command.agreements(), ConsentSource.SIGNUP, Instant.now());
AuthResult result = sessionTokenService.issue(account, profile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import com.heddy.domain.account.exception.AccountException;
import com.heddy.domain.account.model.Account;
import com.heddy.domain.account.model.ConsentSource;
import com.heddy.domain.account.model.HairProfile;
import com.heddy.domain.account.model.UserProfile;
import com.heddy.domain.account.port.in.AuthResult;
import com.heddy.domain.account.port.in.SocialSignupCommand;
import com.heddy.domain.account.port.in.SocialSignupUseCase;
import com.heddy.domain.account.port.out.AccountRepositoryPort;
import com.heddy.domain.account.port.out.ConsentHistoryRepositoryPort;
import com.heddy.domain.account.port.out.HairProfileRepositoryPort;
import com.heddy.domain.account.port.out.SocialTokenVerifierPort;
import com.heddy.domain.account.port.out.UserProfileRepositoryPort;
import lombok.RequiredArgsConstructor;
Expand All @@ -26,6 +28,7 @@ public class SocialSignupService implements SocialSignupUseCase {
private final SocialTokenVerifierPort socialTokenVerifierPort;
private final AccountRepositoryPort accountRepositoryPort;
private final UserProfileRepositoryPort userProfileRepositoryPort;
private final HairProfileRepositoryPort hairProfileRepositoryPort;
private final ConsentHistoryRepositoryPort consentHistoryRepositoryPort;
private final SessionTokenService sessionTokenService;
private final SignupPhoneVerificationService signupPhoneVerificationService;
Expand All @@ -47,6 +50,12 @@ public AuthResult signup(SocialSignupCommand command) {
Account.social(userId, command.provider(), identity.subject()));
UserProfile profile = userProfileRepositoryPort.save(
UserProfile.signup(userId, command.nickname(), command.phone()));
if (command.hairProfile() != null) {
var hairProfile = command.hairProfile();
hairProfileRepositoryPort.save(HairProfile.create(userId, hairProfile.hairType(),
hairProfile.hairCondition(), hairProfile.hairLength(),
hairProfile.hairThickness(), hairProfile.availableCareTimeMinutes()));
}
consentHistoryRepositoryPort.append(
userId, command.agreements(), ConsentSource.SIGNUP, Instant.now());
AuthResult result = sessionTokenService.issue(account, profile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public record EmailSignupCommand(
String password,
String nickname,
String phone,
List<ConsentDecision> agreements
List<ConsentDecision> agreements,
SignupHairProfileCommand hairProfile
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.heddy.domain.account.port.in;

import com.heddy.domain.account.model.HairProfile.HairCondition;
import com.heddy.domain.account.model.HairProfile.HairLength;
import com.heddy.domain.account.model.HairProfile.HairThickness;
import com.heddy.domain.account.model.HairProfile.HairType;

public record SignupHairProfileCommand(
HairType hairType,
HairCondition hairCondition,
HairLength hairLength,
HairThickness hairThickness,
Integer availableCareTimeMinutes
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public record SocialSignupCommand(
String providerToken,
String nickname,
String phone,
List<ConsentDecision> agreements
List<ConsentDecision> agreements,
SignupHairProfileCommand hairProfile
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ void signupRequestExamplesMatchTheApiSpecification() throws Exception {
.andExpect(jsonPath("$['paths']['/auth/signup/email']['post']"
+ "['requestBody']['content']['application/json']['examples']"
+ "['이메일 회원가입 요청']['value']['phone_number']").doesNotExist())
.andExpect(jsonPath("$['paths']['/auth/signup/email']['post']"
+ "['requestBody']['content']['application/json']['examples']"
+ "['이메일 회원가입 요청']['value']['hair_profile']['hair_type']")
.value("STRAIGHT"))
.andExpect(jsonPath("$['paths']['/auth/signup/email']['post']"
+ "['requestBody']['content']['application/json']['examples']"
+ "['이메일 회원가입 요청']['value']['hair_profile']['hair_length']")
.value("SHORT"))
.andExpect(jsonPath("$['paths']['/auth/signup/social']['post']"
+ "['requestBody']['content']['application/json']['examples']"
+ "['소셜 회원가입 요청']['value']['provider']")
Expand All @@ -66,6 +74,22 @@ void signupRequestExamplesMatchTheApiSpecification() throws Exception {
.value(true))
.andExpect(jsonPath("$['paths']['/auth/signup/social']['post']"
+ "['requestBody']['content']['application/json']['examples']"
+ "['소셜 회원가입 요청']['value']['phone_number']").doesNotExist());
+ "['소셜 회원가입 요청']['value']['phone_number']").doesNotExist())
.andExpect(jsonPath("$['paths']['/auth/signup/social']['post']"
+ "['requestBody']['content']['application/json']['examples']"
+ "['소셜 회원가입 요청']['value']['hair_profile']['hair_condition']")
.value("HEALTHY"))
.andExpect(jsonPath("$.components.schemas.EmailSignupRequest.properties"
+ ".hair_profile['$ref']").value("#/components/schemas/HairProfileRequest"))
.andExpect(jsonPath("$.components.schemas.SocialSignupRequest.properties"
+ ".hair_profile['$ref']").value("#/components/schemas/HairProfileRequest"))
.andExpect(jsonPath("$.components.schemas.HairProfileRequest.required",
hasItem("hair_type")))
.andExpect(jsonPath("$.components.schemas.HairProfileRequest.required",
hasItem("hair_condition")))
.andExpect(jsonPath("$.components.schemas.HairProfileRequest.required",
hasItem("hair_length")))
.andExpect(jsonPath("$.components.schemas.HairProfileRequest.required",
hasItem("hair_thickness")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ void emailSignupReturnsUuidUserAndSnakeCaseTokens() throws Exception {
"ai_training":false,
"service_analytics":true,
"marketing_notification":false
},
"hair_profile":{
"hair_type":"STRAIGHT",
"hair_condition":"HEALTHY",
"hair_length":"SHORT",
"hair_thickness":"THIN",
"available_care_time_minutes":15
}
}
"""))
Expand All @@ -109,6 +116,74 @@ void emailSignupReturnsUuidUserAndSnakeCaseTokens() throws Exception {
.andExpect(jsonPath("$.data.tokens.token_type").value("Bearer"))
.andExpect(jsonPath("$.data.tokens.expires_in").value(900))
.andExpect(jsonPath("$.request_id").value("request-2"));

verify(emailSignupUseCase).signup(org.mockito.ArgumentMatchers.argThat(command ->
command.hairProfile() != null
&& command.hairProfile().hairType().name().equals("STRAIGHT")
&& command.hairProfile().hairCondition().name().equals("HEALTHY")
&& command.hairProfile().hairLength().name().equals("SHORT")
&& command.hairProfile().hairThickness().name().equals("THIN")
&& command.hairProfile().availableCareTimeMinutes() == 15));
}

@Test
void socialSignupMapsHairProfile() throws Exception {
given(socialSignupUseCase.signup(any())).willReturn(authResult());

mockMvc.perform(post("/auth/signup/social")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"provider":"GOOGLE",
"provider_token":"provider-token",
"nickname":"헤디",
"agreements":{
"terms_of_service":true,
"privacy_policy":true,
"ai_training":false,
"service_analytics":true,
"marketing_notification":false
},
"hair_profile":{
"hair_type":"WAVY",
"hair_condition":"NORMAL",
"hair_length":"BELOW_SHOULDER",
"hair_thickness":"NORMAL",
"available_care_time_minutes":20
}
}
"""))
.andExpect(status().isCreated());

verify(socialSignupUseCase).signup(org.mockito.ArgumentMatchers.argThat(command ->
command.hairProfile() != null
&& command.hairProfile().hairType().name().equals("WAVY")
&& command.hairProfile().availableCareTimeMinutes() == 20));
}

@Test
void rejectsIncompleteSignupHairProfile() throws Exception {
mockMvc.perform(post("/auth/signup/email")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"email":"user@example.com",
"password":"Password123",
"nickname":"헤디",
"agreements":{
"terms_of_service":true,
"privacy_policy":true,
"ai_training":false,
"service_analytics":true,
"marketing_notification":false
},
"hair_profile":{
"hair_type":"STRAIGHT"
}
}
"""))
.andExpect(status().isUnprocessableEntity())
.andExpect(jsonPath("$.error.code").value("VALIDATION_FAILED"));
}

@Test
Expand Down
Loading
Loading