diff --git a/build.gradle b/build.gradle index 99ddb470..55d7bc5e 100644 --- a/build.gradle +++ b/build.gradle @@ -29,16 +29,17 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' - testImplementation 'org.projectlombok:lombok' - testImplementation 'org.projectlombok:lombok' - testCompileOnly 'org.projectlombok:lombok' - testAnnotationProcessor 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // Lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' + testImplementation 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' + // 인증사 관련 의존성 implementation 'javax.servlet:jstl:1.2' implementation "org.apache.tomcat.embed:tomcat-embed-jasper" @@ -69,7 +70,6 @@ dependencies { testImplementation 'org.testcontainers:junit-jupiter:1.19.3' testImplementation 'org.testcontainers:mysql:1.20.0' - // security implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' @@ -103,11 +103,6 @@ dependencies { runtimeOnly 'com.h2database:h2' - testImplementation 'org.springframework.boot:spring-boot-testcontainers:3.3.5' - testImplementation 'org.testcontainers:testcontainers:1.19.3' - testImplementation 'org.testcontainers:junit-jupiter:1.19.3' - testImplementation 'org.testcoscntainers:mysql:1.20.0' - annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" implementation 'org.apache.commons:commons-pool2:2.12.1' diff --git a/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java new file mode 100644 index 00000000..3e823364 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java @@ -0,0 +1,47 @@ +package life.mosu.mosuserver.application.oauth; + +import life.mosu.mosuserver.domain.user.entity.AuthProvider; +import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; +import life.mosu.mosuserver.domain.user.entity.UserRole; +import life.mosu.mosuserver.domain.user.repository.UserJpaRepository; +import life.mosu.mosuserver.global.processor.StepProcessor; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class OAuthUserPersistenceProcessor implements StepProcessor { + + private final UserJpaRepository userRepository; + + @Override + @Transactional + public UserJpaEntity process(final OAuthUserInfo info) { + return userRepository.findByLoginId(info.email()) + .map(existingUser -> { + existingUser.updateOAuthUser( + info.gender(), + info.name(), + info.phoneNumber(), + info.birthDay(), + info.marketingAgreed()); + return existingUser; + }) + .orElseGet(() -> { + final UserJpaEntity newUser = UserJpaEntity.builder() + .loginId(info.email()) + .gender(info.gender()) + .name(info.name()) + .birth(info.birthDay()) + .phoneNumber(info.phoneNumber()) + .userRole(UserRole.ROLE_PENDING) + .provider(AuthProvider.KAKAO) + .agreedToTermsOfService(true) + .agreedToPrivacyPolicy(true) + .agreedToMarketing(info.marketingAgreed()) + .build(); + return userRepository.save(newUser); + }); + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java index f67b968d..3c0ddf67 100644 --- a/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java +++ b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java @@ -1,15 +1,10 @@ package life.mosu.mosuserver.application.oauth; -import java.time.LocalDate; import java.util.Collections; import java.util.List; import java.util.Map; -import life.mosu.mosuserver.domain.profile.entity.Gender; import life.mosu.mosuserver.domain.profile.repository.ProfileJpaRepository; -import life.mosu.mosuserver.domain.user.entity.AuthProvider; import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; -import life.mosu.mosuserver.domain.user.entity.UserRole; -import life.mosu.mosuserver.domain.user.repository.UserJpaRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; @@ -25,7 +20,7 @@ @RequiredArgsConstructor public class OAuthUserService extends DefaultOAuth2UserService { - private final UserJpaRepository userRepository; + private final OAuthUserPersistenceProcessor oAuthUserPersistenceProcessor; private final ProfileJpaRepository profileRepository; private final WebClient webClient; @@ -44,12 +39,15 @@ public OAuth2User loadUser(final OAuth2UserRequest userRequest) agreedToMarketing = termsList.stream() .filter(term -> term instanceof Map) .map(term -> (Map) term) - .filter(termMap -> "terms_03".equals(termMap.get("tag"))) + .filter(termMap -> + "terms_03".equals(termMap.get("tag"))) .findFirst() .map(termMap -> (Boolean) termMap.get("agreed")) .orElse(false); } + log.info("동의 여부{}", agreedToMarketing); + final String registrationId = userRequest.getClientRegistration().getRegistrationId(); final String userNameAttributeName = userRequest.getClientRegistration() .getProviderDetails() @@ -59,7 +57,7 @@ public OAuth2User loadUser(final OAuth2UserRequest userRequest) final OAuthUserInfo userInfo = OAuthUserInfo.of(OAuthProvider.from(registrationId), oAuth2UserAttributes, agreedToMarketing); - final UserJpaEntity oAuthUser = updateOrWrite(userInfo); + final UserJpaEntity oAuthUser = oAuthUserPersistenceProcessor.process(userInfo); Boolean isProfileRegistered = profileRepository.existsByUserId(oAuthUser.getId()); @@ -67,35 +65,6 @@ public OAuth2User loadUser(final OAuth2UserRequest userRequest) isProfileRegistered); } - private UserJpaEntity updateOrWrite(final OAuthUserInfo info) { - return userRepository.findByLoginId(info.email()) - .map(existingUser -> { - existingUser.updateOAuthUser( - info.gender(), - info.name(), - info.phoneNumber(), - info.birthDay() != null ? info.birthDay() : LocalDate.of(1900, 1, 1)); - return existingUser; - }) - .orElseGet(() -> { - final UserJpaEntity newUser = UserJpaEntity.builder() - .loginId(info.email() != null ? info.email() : "NA") - .gender(info.gender() != null ? info.gender() : Gender.PENDING) - .name(info.name() != null ? info.name() : "NA") - .birth(info.birthDay() != null ? info.birthDay() - : LocalDate.EPOCH) - .phoneNumber(info.phoneNumber() != null ? info.phoneNumber() - : "010-0000-0000") - .userRole(UserRole.ROLE_PENDING) - .provider(AuthProvider.KAKAO) - .agreedToTermsOfService(true) - .agreedToPrivacyPolicy(true) - .agreedToMarketing(info.marketingAgreed()) - .build(); - return userRepository.save(newUser); - }); - } - private Map getServiceTerms(String accessToken) { String url = "https://kapi.kakao.com/v2/user/service_terms"; diff --git a/src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java index 280b4a13..a936bcc2 100644 --- a/src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java @@ -93,12 +93,14 @@ public void updateOAuthUser( Gender gender, String name, String phoneNumber, - LocalDate birth + LocalDate birth, + boolean agreedToMarketing ) { this.gender = gender; this.name = name; this.phoneNumber = phoneNumber; this.birth = birth; + this.agreedToMarketing = agreedToMarketing; } public void updateUserInfo( diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql new file mode 100644 index 00000000..5b9166af --- /dev/null +++ b/src/main/resources/db/migration/V1__init.sql @@ -0,0 +1,383 @@ +CREATE TABLE application +( + application_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + deleted BIT(1) NULL, + user_id BIGINT NULL, + parent_phone_number VARCHAR(255) NULL, + application_status VARCHAR(255) NOT NULL, + agreed_to_notices BIT(1) NULL, + agreed_to_refund_policy BIT(1) NULL, + CONSTRAINT pk_application PRIMARY KEY (application_id) +); + +CREATE TABLE application_failure_log +( + application_failure_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + application_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + reason VARCHAR(255) NOT NULL, + snapshot TEXT NULL, + CONSTRAINT pk_application_failure_log PRIMARY KEY (application_failure_id) +); + +CREATE TABLE banner +( + deleted BIT(1) NOT NULL, + id BIGINT AUTO_INCREMENT NOT NULL, + file_name VARCHAR(255) NULL, + s3key VARCHAR(255) NULL, + visibility VARCHAR(255) NULL, + created_at datetime NULL, + updated_at datetime NULL, + title VARCHAR(255) NULL, + dead_line datetime NULL, + banner_link VARCHAR(255) NULL, + CONSTRAINT pk_banner PRIMARY KEY (id) +); + +CREATE TABLE blocked_ip_history_log +( + id BIGINT AUTO_INCREMENT NOT NULL, + ip VARCHAR(255) NULL, + penalty_level VARCHAR(255) NULL, + blocked_at datetime NULL, + CONSTRAINT pk_blocked_ip_history_log PRIMARY KEY (id) +); + +CREATE TABLE event +( + deleted BIT(1) NOT NULL, + event_id BIGINT AUTO_INCREMENT NOT NULL, + file_name VARCHAR(255) NULL, + s3key VARCHAR(255) NULL, + visibility VARCHAR(255) NULL, + created_at datetime NULL, + updated_at datetime NULL, + event_title VARCHAR(255) NOT NULL, + event_link VARCHAR(255) NULL, + start_date date NULL, + end_date date NULL, + CONSTRAINT pk_event PRIMARY KEY (event_id) +); + +CREATE TABLE exam +( + deleted BIT(1) NOT NULL, + id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + school_name VARCHAR(255) NULL, + area VARCHAR(255) NULL, + capacity INT NULL, + deadline_time datetime NULL, + exam_date date NOT NULL, + lunch_name VARCHAR(255) NULL, + lunch_price INT NULL, + exam_status VARCHAR(255) NOT NULL, + zipcode VARCHAR(255) NULL, + street VARCHAR(255) NULL, + detail VARCHAR(255) NULL, + CONSTRAINT pk_exam PRIMARY KEY (id) +); + +CREATE TABLE exam_application +( + id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + deleted BIT(1) NULL, + application_id BIGINT NULL, + user_id BIGINT NULL, + exam_id BIGINT NULL, + lunch_checked BIT(1) NULL, + exam_number VARCHAR(255) NULL, + CONSTRAINT pk_exam_application PRIMARY KEY (id) +); + +CREATE TABLE exam_subject +( + id BIGINT AUTO_INCREMENT NOT NULL, + exam_application_id BIGINT NULL, + subject VARCHAR(255) NULL, + CONSTRAINT pk_exam_subject PRIMARY KEY (id) +); + +CREATE TABLE exam_ticket_image +( + exam_ticket_image_id BIGINT AUTO_INCREMENT NOT NULL, + file_name VARCHAR(255) NULL, + s3key VARCHAR(255) NULL, + visibility VARCHAR(255) NULL, + application_id BIGINT NOT NULL, + CONSTRAINT pk_exam_ticket_image PRIMARY KEY (exam_ticket_image_id) +); + +CREATE TABLE faq +( + deleted BIT(1) NOT NULL, + faq_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + question VARCHAR(500) NOT NULL, + answer VARCHAR(255) NOT NULL, + author VARCHAR(255) NOT NULL, + user_id BIGINT NOT NULL, + CONSTRAINT pk_faq PRIMARY KEY (faq_id) +); + +CREATE TABLE file_move_fail_log +( + id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + faq_id BIGINT NULL, + s3key VARCHAR(255) NULL, + destination_folder SMALLINT NULL, + CONSTRAINT pk_filemovefaillog PRIMARY KEY (id) +); + +CREATE TABLE form +( + form_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + exam_date date NOT NULL, + org_name VARCHAR(255) NULL, + password VARCHAR(255) NULL, + user_name VARCHAR(255) NULL, + gender VARCHAR(255) NULL, + birth date NULL, + phone_number VARCHAR(255) NULL, + subjects VARCHAR(255) NULL, + subjects2 VARCHAR(255) NULL, + lunch BIT(1) NULL, + area VARCHAR(255) NULL, + school_name VARCHAR(255) NULL, + file_name VARCHAR(255) NULL, + s3_key VARCHAR(255) NULL, + CONSTRAINT pk_form PRIMARY KEY (form_id) +); + +CREATE TABLE inquiry +( + deleted BIT(1) NOT NULL, + inquiry_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + title VARCHAR(300) NOT NULL, + content VARCHAR(1000) NOT NULL, + user_id BIGINT NOT NULL, + author VARCHAR(255) NULL, + status VARCHAR(255) NULL, + CONSTRAINT pk_inquiry PRIMARY KEY (inquiry_id) +); + +CREATE TABLE inquiry_answer +( + deleted BIT(1) NOT NULL, + inquiry_answer_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + title VARCHAR(300) NOT NULL, + content VARCHAR(1000) NOT NULL, + inquiry_id BIGINT NOT NULL, + author VARCHAR(255) NOT NULL, + user_id BIGINT NOT NULL, + CONSTRAINT pk_inquiry_answer PRIMARY KEY (inquiry_answer_id) +); + +CREATE TABLE inquiry_answer_attachment +( + inquiry_answer_attachment_id BIGINT AUTO_INCREMENT NOT NULL, + file_name VARCHAR(255) NULL, + s3key VARCHAR(255) NULL, + visibility VARCHAR(255) NULL, + inquiry_answer_id BIGINT NOT NULL, + CONSTRAINT pk_inquiry_answer_attachment PRIMARY KEY (inquiry_answer_attachment_id) +); + +CREATE TABLE inquiry_attachment +( + inquiry_attachment_id BIGINT AUTO_INCREMENT NOT NULL, + file_name VARCHAR(255) NULL, + s3key VARCHAR(255) NULL, + visibility VARCHAR(255) NULL, + inquiry_id BIGINT NOT NULL, + CONSTRAINT pk_inquiry_attachment PRIMARY KEY (inquiry_attachment_id) +); + +CREATE TABLE notice +( + deleted BIT(1) NOT NULL, + notice_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + title VARCHAR(255) NOT NULL, + content VARCHAR(3000) NOT NULL, + user_id BIGINT NOT NULL, + author VARCHAR(255) NOT NULL, + CONSTRAINT pk_notice PRIMARY KEY (notice_id) +); + +CREATE TABLE notice_attachment +( + notice_attachment_id BIGINT AUTO_INCREMENT NOT NULL, + file_name VARCHAR(255) NULL, + s3key VARCHAR(255) NULL, + visibility VARCHAR(255) NULL, + notice_id BIGINT NOT NULL, + CONSTRAINT pk_notice_attachment PRIMARY KEY (notice_attachment_id) +); + +CREATE TABLE notify +( + deleted BIT(1) NOT NULL, + id BIGINT AUTO_INCREMENT NOT NULL, + notify_custom_key VARCHAR(255) NOT NULL, + notify_type VARCHAR(255) NOT NULL, + notify_result_code VARCHAR(255) NOT NULL, + CONSTRAINT pk_notify PRIMARY KEY (id) +); + +CREATE TABLE payment +( + payment_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + deleted BIT(1) NULL, + exam_application_id BIGINT NULL, + application_id BIGINT NULL, + payment_key VARCHAR(255) NULL, + order_id VARCHAR(255) NOT NULL, + status VARCHAR(255) NOT NULL, + method VARCHAR(255) NULL, + total_amount INT NULL, + supplied_amount INT NULL, + vat_amount INT NULL, + balance_amount INT NULL, + tax_free_amount INT NULL, + CONSTRAINT pk_payment PRIMARY KEY (payment_id) +); + +CREATE TABLE payment_failure_log +( + payment_failure_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + payment_id BIGINT NOT NULL, + exam_application_id BIGINT NOT NULL, + application_id BIGINT NULL, + reason VARCHAR(255) NOT NULL, + snapshot TEXT NULL, + CONSTRAINT pk_payment_failure_log PRIMARY KEY (payment_failure_id) +); + +CREATE TABLE profile +( + deleted BIT(1) NOT NULL, + profile_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + user_id BIGINT NOT NULL, + user_name VARCHAR(255) NOT NULL, + gender VARCHAR(255) NOT NULL, + birth date NOT NULL, + phone_number VARCHAR(255) NOT NULL, + email VARCHAR(255) NULL, + education VARCHAR(255) NULL, + recommender_phone_number VARCHAR(255) NULL, + grade VARCHAR(255) NULL, + school_name VARCHAR(255) NULL, + zipcode VARCHAR(255) NULL, + street VARCHAR(255) NULL, + CONSTRAINT pk_profile PRIMARY KEY (profile_id) +); + +CREATE TABLE recommendation +( + deleted BIT(1) NOT NULL, + recommendation_id BIGINT AUTO_INCREMENT NOT NULL, + user_id BIGINT NULL, + recommeded_name VARCHAR(255) NULL, + recommeded_phone_number VARCHAR(255) NULL, + bank VARCHAR(255) NULL, + account_number VARCHAR(255) NULL, + CONSTRAINT pk_recommendation PRIMARY KEY (recommendation_id) +); + +CREATE TABLE refund +( + refund_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + deleted BIT(1) NULL, + transaction_key VARCHAR(255) NOT NULL, + exam_application_id BIGINT NULL, + reason VARCHAR(255) NOT NULL, + refund_status VARCHAR(255) NULL, + refunded_amount INT NULL, + refundable_amount INT NULL, + CONSTRAINT pk_refund PRIMARY KEY (refund_id) +); + +CREATE TABLE refund_failure_log +( + refund_failure_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + refund_id BIGINT NOT NULL, + exam_application_id BIGINT NOT NULL, + reason VARCHAR(255) NOT NULL, + snapshot TEXT NULL, + CONSTRAINT pk_refund_failure_log PRIMARY KEY (refund_failure_id) +); + +CREATE TABLE user +( + deleted BIT(1) NOT NULL, + user_id BIGINT AUTO_INCREMENT NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + login_id VARCHAR(50) NULL, + password VARCHAR(255) NULL, + gender VARCHAR(255) NULL, + name VARCHAR(255) NULL, + birth date NULL, + phone_number VARCHAR(255) NULL, + customer_key VARCHAR(255) NULL, + agreed_to_terms_of_service BIT(1) NULL, + agreed_to_privacy_policy BIT(1) NULL, + agreed_to_marketing BIT(1) NULL, + user_role VARCHAR(50) NOT NULL, + provider VARCHAR(255) NULL, + CONSTRAINT pk_user PRIMARY KEY (user_id) +); + +CREATE TABLE virtual_account_log +( + deleted BIT(1) NOT NULL, + virtual_account_log_id BIGINT AUTO_INCREMENT NOT NULL, + application_id BIGINT NULL, + order_id VARCHAR(255) NULL, + account_number VARCHAR(255) NULL, + bank_name VARCHAR(255) NULL, + customer_name VARCHAR(255) NULL, + customer_email VARCHAR(255) NULL, + deposit_status SMALLINT NULL, + CONSTRAINT pk_virtual_account_log PRIMARY KEY (virtual_account_log_id) +); + +ALTER TABLE profile + ADD CONSTRAINT uc_25d92281884ae5fdff0d3ec10 UNIQUE (user_id); + +ALTER TABLE notify + ADD CONSTRAINT uc_notify_notify_custom_key UNIQUE (notify_custom_key); + +ALTER TABLE user + ADD CONSTRAINT uc_user_login UNIQUE (login_id); + +CREATE INDEX idx_status_created_at ON payment (status, created_at); \ No newline at end of file diff --git a/src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java b/src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java new file mode 100644 index 00000000..e38ce290 --- /dev/null +++ b/src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java @@ -0,0 +1,44 @@ +package life.mosu.mosuserver.application.oauth; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("카카오_추가기능_응답_테스트") +class OAuthUserServiceTest { + + @Test + @DisplayName("Service Terms 응답에 마케팅 동의(terms_03)가 없는 경우 false로 파싱한다") + void 마케팅_동의_여부를_파싱한다() { + Map term1 = Map.of( + "tag", "terms_02", + "required", true, + "agreed", true + ); + Map term2 = Map.of( + "tag", "terms_01", + "required", true, + "agreed", true + ); + + Map serviceTermsAttributes = new HashMap<>(); + serviceTermsAttributes.put("id", 4342056184L); + serviceTermsAttributes.put("service_terms", List.of(term1, term2)); + + boolean agreedToMarketing = false; + if (serviceTermsAttributes.get("service_terms") instanceof List termsList) { + agreedToMarketing = termsList.stream() + .filter(term -> term instanceof Map) + .map(term -> (Map) term) + .filter(termMap -> "terms_03".equals(termMap.get("tag"))) + .findFirst() + .map(termMap -> (Boolean) termMap.get("agreed")) + .orElse(false); + } + + Assertions.assertFalse(agreedToMarketing); + } +} \ No newline at end of file