-
Notifications
You must be signed in to change notification settings - Fork 2
Prod 1: 추천인 기능 배포 #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b5c097c
dc5689a
40fbcd3
025ea0d
c0880fa
0e576a0
0275cb9
6eb82e9
8fd7e9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package life.mosu.mosuserver.application.profile; | ||
|
|
||
| import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; | ||
| import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; | ||
| import life.mosu.mosuserver.global.exception.CustomRuntimeException; | ||
| import life.mosu.mosuserver.global.exception.ErrorCode; | ||
| import life.mosu.mosuserver.presentation.profile.dto.RecommenderRegistrationRequest; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class RecommenderService { | ||
|
|
||
| private final ProfileJpaRepository profileJpaRepository; | ||
|
|
||
| @Transactional | ||
| public void registerRecommender(Long userId, RecommenderRegistrationRequest request) { | ||
| ProfileJpaEntity profile = profileJpaRepository.findByUserId(userId) | ||
| .orElseThrow(() -> new CustomRuntimeException(ErrorCode.PROFILE_NOT_FOUND)); | ||
| if (profile.getRecommenderPhoneNumber() != null) { | ||
| throw new CustomRuntimeException(ErrorCode.ALREADY_REGISTERED_RECOMMENDER); | ||
| } | ||
|
|
||
| profile.registerRecommenderPhoneNumber(request.phoneNumber()); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED) | ||
| public Boolean verifyRecommender(Long userId) { | ||
| ProfileJpaEntity profile = profileJpaRepository.findByUserId(userId) | ||
| .orElseThrow(() -> new CustomRuntimeException(ErrorCode.PROFILE_NOT_FOUND)); | ||
| return profile.getRecommenderPhoneNumber() != null; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,28 +29,22 @@ public class ApplicationJpaEntity extends BaseTimeEntity { | |
| @Column(name = "guardian_phone_number") | ||
| private String guardianPhoneNumber; | ||
|
|
||
| @Column(name = "recommender_phone_number") | ||
| private String recommenderPhoneNumber; | ||
|
|
||
| @Column(name = "agreed_to_notices") | ||
| private Boolean agreedToNotices; | ||
|
|
||
| @Column(name = "agreed_to_refund_policy") | ||
| private Boolean agreedToRefundPolicy; | ||
|
Comment on lines
32
to
36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
|
|
||
| @Builder | ||
| public ApplicationJpaEntity( | ||
| final Long userId, | ||
| final String guardianPhoneNumber, | ||
| final String recommenderPhoneNumber, | ||
| final boolean agreedToNotices, | ||
| final boolean agreedToRefundPolicy | ||
|
|
||
| ) { | ||
| this.userId = userId; | ||
| this.guardianPhoneNumber = guardianPhoneNumber; | ||
| this.recommenderPhoneNumber = recommenderPhoneNumber; | ||
| this.agreedToNotices = agreedToNotices; | ||
| this.agreedToRefundPolicy = agreedToRefundPolicy; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,6 @@ | |
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.http.HttpMethod; | ||
| import org.springframework.security.config.Customizer; | ||
| import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
|
|
@@ -71,7 +70,8 @@ public WebSecurityCustomizer webSecurityCustomizer() { | |
| @Bean | ||
| public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception { | ||
| http.csrf(AbstractHttpConfigurer::disable) | ||
| .cors(Customizer.withDefaults()) | ||
| // .cors(Customizer.withDefaults()) | ||
| .cors(AbstractHttpConfigurer::disable) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disabling CORS completely ( // TODO: Re-enable CORS with proper configuration before deploying to production
// http.cors(Customizer.withDefaults());
http.cors(AbstractHttpConfigurer::disable); |
||
| .httpBasic(AbstractHttpConfigurer::disable) | ||
| .headers(c -> c.frameOptions(FrameOptionsConfig::disable)) | ||
| .sessionManagement( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,32 @@ | ||
| package life.mosu.mosuserver.global.config; | ||
|
|
||
| import java.util.List; | ||
| import life.mosu.mosuserver.global.resolver.UserIdArgumentResolver; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||
| import org.springframework.web.servlet.config.annotation.CorsRegistry; | ||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Configuration | ||
| @RequiredArgsConstructor | ||
| public class WebMvcConfig implements WebMvcConfigurer { | ||
|
|
||
| private final UserIdArgumentResolver resolver; | ||
|
|
||
| @Override | ||
| public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) { | ||
| resolvers.add(resolver); | ||
| } | ||
|
|
||
| @Override | ||
| public void addCorsMappings(CorsRegistry registry) { | ||
| registry.addMapping("/**") | ||
| .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") | ||
| .allowedHeaders("*") | ||
| .allowedOrigins("http://localhost:3000", "http://localhost:8080", | ||
| "http://api.mosuedu.com", "https://api.mosuedu.com") | ||
| .allowCredentials(true) | ||
|
Comment on lines
+24
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using environment variables or configuration properties to manage the allowed origins instead of hardcoding them. This allows for easier configuration changes without requiring code deployments. For example, you can define a property String[] allowedOrigins = {"http://localhost:3000", "http://localhost:8080",
"http://api.mosuedu.com", "https://api.mosuedu.com"};
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.allowedOrigins(allowedOrigins)
.allowCredentials(true)
.maxAge(3600); |
||
| .maxAge(3600); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| package life.mosu.mosuserver.global.initializer; | ||
|
|
||
| import static life.mosu.mosuserver.domain.user.UserRole.ROLE_ADMIN; | ||
| import static life.mosu.mosuserver.domain.user.UserRole.ROLE_USER; | ||
|
|
||
| import jakarta.annotation.PostConstruct; | ||
| import java.time.LocalDate; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Random; | ||
| import life.mosu.mosuserver.domain.profile.Education; | ||
| import life.mosu.mosuserver.domain.profile.Gender; | ||
| import life.mosu.mosuserver.domain.profile.Grade; | ||
| import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; | ||
| import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; | ||
| import life.mosu.mosuserver.domain.profile.SchoolInfoJpaVO; | ||
| import life.mosu.mosuserver.domain.user.UserJpaEntity; | ||
| import life.mosu.mosuserver.domain.user.UserJpaRepository; | ||
| import life.mosu.mosuserver.domain.user.UserRole; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class UserAndProfileInitializer { | ||
|
|
||
| private final UserJpaRepository userRepository; | ||
| private final ProfileJpaRepository profileRepository; | ||
| private final PasswordEncoder passwordEncoder; | ||
|
|
||
| @PostConstruct | ||
| public void init() { | ||
| if (userRepository.count() > 0 || profileRepository.count() > 0) { | ||
| log.info("이미 더미 데이터가 존재하여 초기화를 건너뜝니다."); | ||
| return; | ||
| } | ||
|
|
||
| List<UserJpaEntity> createdUsers = new ArrayList<>(); | ||
| Random random = new Random(); | ||
|
|
||
| for (int i = 1; i <= 10; i++) { | ||
| String loginId = "user" + i; | ||
| String name = (i % 2 == 0) ? "김철수" + i : "이영희" + i; | ||
| Gender gender = (i % 2 == 0) ? Gender.MALE : Gender.FEMALE; | ||
| LocalDate birth = LocalDate.of(1990 + (i % 5), (i % 12) + 1, (i % 28) + 1); | ||
| String customerKey = "CK-" + i + "-" + System.currentTimeMillis(); | ||
| boolean agreedToMarketing = random.nextBoolean(); | ||
| UserRole userRole = (i == 1) ? ROLE_ADMIN : ROLE_USER; | ||
|
|
||
| UserJpaEntity user = UserJpaEntity.builder() | ||
| .loginId(loginId) | ||
| .password(passwordEncoder.encode("password" + i + "!")) | ||
| .gender(gender) | ||
| .name(name) | ||
| .birth(birth) | ||
| .customerKey(customerKey) | ||
| .agreedToTermsOfService(true) | ||
| .agreedToPrivacyPolicy(true) | ||
| .agreedToMarketing(agreedToMarketing) | ||
| .userRole(userRole) | ||
| .build(); | ||
|
|
||
| createdUsers.add(userRepository.save(user)); | ||
|
|
||
| String phoneNumber = | ||
| "010-" + String.format("%04d", i) + "-" + String.format("%04d", i + 1000); | ||
| String email = "user" + i + "@example.com"; | ||
| Education education = Education.values()[random.nextInt(Education.values().length)]; | ||
| Grade grade = Grade.values()[random.nextInt(Grade.values().length)]; | ||
| SchoolInfoJpaVO schoolInfo = new SchoolInfoJpaVO(("모수대학교" + (i % 3 + 1)), "123-23", | ||
| "서울시 모수구 모수동"); | ||
| String recommenderPhoneNumber = (i % 3 == 0) ? "010-1234-5678" : null; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| ProfileJpaEntity profile = ProfileJpaEntity.builder() | ||
| .userId(user.getId()) | ||
| .userName(user.getName()) | ||
| .gender(user.getGender()) | ||
| .birth(user.getBirth()) | ||
| .phoneNumber(phoneNumber) | ||
| .email(email) | ||
| .education(education) | ||
| .schoolInfo(schoolInfo) | ||
| .grade(grade) | ||
| .build(); | ||
|
|
||
| profile.registerRecommenderPhoneNumber(recommenderPhoneNumber); | ||
|
|
||
| profileRepository.save(profile); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package life.mosu.mosuserver.presentation.profile; | ||
|
|
||
| import jakarta.validation.Valid; | ||
| import life.mosu.mosuserver.application.profile.RecommenderService; | ||
| import life.mosu.mosuserver.global.util.ApiResponseWrapper; | ||
| import life.mosu.mosuserver.presentation.profile.dto.RecommenderRegistrationRequest; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| 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.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/recommender") | ||
| public class RecommenderController implements RecommenderControllerDocs { | ||
|
|
||
| private final RecommenderService recommenderService; | ||
|
|
||
| @Override | ||
| @PostMapping | ||
| public ResponseEntity<ApiResponseWrapper<Void>> register( | ||
| @RequestParam Long userId, | ||
| @Valid @RequestBody RecommenderRegistrationRequest request) { | ||
| recommenderService.registerRecommender(userId, request); | ||
| return ResponseEntity.status(HttpStatus.CREATED) | ||
| .body(ApiResponseWrapper.success(HttpStatus.CREATED, "추천인 등록 성공")); | ||
| } | ||
|
|
||
| @Override | ||
| @GetMapping("/verify") | ||
| public ResponseEntity<ApiResponseWrapper<Boolean>> verify( | ||
| @RequestParam Long userId) { | ||
| Boolean isRegistered = recommenderService.verifyRecommender(userId); | ||
|
|
||
| if (isRegistered) { | ||
| return ResponseEntity.ok( | ||
| ApiResponseWrapper.success(HttpStatus.OK, "이미 추천인이 등록되었습니다.", isRegistered)); | ||
| } | ||
| return ResponseEntity.ok( | ||
| ApiResponseWrapper.success(HttpStatus.OK, "추천인을 등록할 수 있습니다.", isRegistered)); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider throwing a more specific exception or creating a custom exception to better reflect the business logic. This can improve error handling and provide more context to the caller.