diff --git a/src/main/java/poomasi/domain/auth/config/CorsConfig.java b/src/main/java/poomasi/domain/auth/config/CorsConfig.java new file mode 100644 index 00000000..fe09e981 --- /dev/null +++ b/src/main/java/poomasi/domain/auth/config/CorsConfig.java @@ -0,0 +1,36 @@ +package poomasi.domain.auth.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; + +@Configuration +public class CorsConfig { + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + + // 허용할 origin 목록 설정 + config.setAllowedOrigins(Arrays.asList( + "https://localhost:3000", + "https://poomasi.shop", + "https://*.poomasi.shop" + )); + + config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); + config.setAllowedHeaders(Arrays.asList("*")); + config.setAllowCredentials(true); + config.setExposedHeaders(Arrays.asList("Set-Cookie", "Authorization")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); // 모든 경로에 대해 적용 + + return source; + } +} + diff --git a/src/main/java/poomasi/domain/auth/config/SecurityBeanGenerator.java b/src/main/java/poomasi/domain/auth/config/SecurityBeanGenerator.java index 2aa3d5d6..de30f9d0 100644 --- a/src/main/java/poomasi/domain/auth/config/SecurityBeanGenerator.java +++ b/src/main/java/poomasi/domain/auth/config/SecurityBeanGenerator.java @@ -2,20 +2,13 @@ import jdk.jfr.Description; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; -import poomasi.domain.auth.token.blacklist.service.TokenBlacklistService; -import poomasi.domain.auth.token.refreshtoken.service.TokenStorageService; -import poomasi.domain.auth.token.util.JwtUtil; -import poomasi.domain.auth.token.refreshtoken.service.TokenRedisService; -import poomasi.domain.member.service.MemberService; + @RequiredArgsConstructor @Configuration diff --git a/src/main/java/poomasi/domain/auth/config/SecurityConfig.java b/src/main/java/poomasi/domain/auth/config/SecurityConfig.java index 14acee13..653f0d68 100644 --- a/src/main/java/poomasi/domain/auth/config/SecurityConfig.java +++ b/src/main/java/poomasi/domain/auth/config/SecurityConfig.java @@ -1,6 +1,8 @@ package poomasi.domain.auth.config; +import jakarta.servlet.http.HttpServletRequest; import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,17 +17,20 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import poomasi.domain.auth.security.filter.CustomUsernamePasswordAuthenticationFilter; import poomasi.domain.auth.security.filter.JwtAuthenticationFilter; import poomasi.domain.auth.security.handler.CustomSuccessHandler; import poomasi.domain.auth.security.userdetail.OAuth2UserDetailServiceImpl; -import poomasi.domain.auth.security.handler.*; import poomasi.domain.auth.security.userdetail.UserDetailsServiceImpl; import poomasi.domain.auth.token.util.JwtUtil; +import java.util.Arrays; +import java.util.Collections; + @AllArgsConstructor @Configuration @@ -38,6 +43,7 @@ public class SecurityConfig { private final MvcRequestMatcher.Builder mvc; private final CustomSuccessHandler customSuccessHandler; private final UserDetailsServiceImpl userDetailsService; + private final CorsConfigurationSource corsConfigurationSource; @Autowired private OAuth2UserDetailServiceImpl oAuth2UserDetailServiceImpl; @@ -60,8 +66,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti //csrf 해제 http.csrf(AbstractHttpConfigurer::disable); - //cors 해제 - http.cors(AbstractHttpConfigurer::disable); + //cors 설정 + http.cors(cors -> cors + .configurationSource(corsConfigurationSource)); //세션 해제 http.sessionManagement((session) -> session @@ -78,23 +85,28 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(HttpMethod.GET, "/api/review/**").permitAll() .requestMatchers(HttpMethod.GET, "/health").permitAll() .requestMatchers(HttpMethod.GET, "/api/image/**").permitAll() - .requestMatchers("/api/sign-up", "/api/login", "api/reissue", "api/payment/**", "api/order/**", "api/reservation/**", "/api/v1/farmer/reservations").permitAll() + .requestMatchers("/api/member/sign-up", "/api/login", "api/reissue", "api/payment/**", "api/order/**", "api/reservation/**", "/api/v1/farmer/reservations").permitAll() .requestMatchers("/api/need-auth/**").authenticated() .anyRequest(). authenticated() ); - /* - http.authorizeHttpRequests((authorize) -> authorize - .requestMatchers("/**").permitAll() - .requestMatchers("/api/auth-test/**", - "/api/cart/**", - "/api/order/**", - "/api/payment/**").authenticated() - .anyRequest() - .authenticated() - ); - */ + + //endpoint : {domain}/oauth2/authentication/kakao + http + .oauth2Login((oauth2) -> oauth2 + .userInfoEndpoint((userInfoEndpointConfig) -> userInfoEndpointConfig + .userService(oAuth2UserDetailServiceImpl)) + .successHandler(customSuccessHandler) + ); + + CustomUsernamePasswordAuthenticationFilter customUsernameFilter = + new CustomUsernamePasswordAuthenticationFilter(authenticationManager(authenticationConfiguration), jwtUtil); + customUsernameFilter.setFilterProcessesUrl("/api/login"); + + http.addFilterAt(customUsernameFilter, UsernamePasswordAuthenticationFilter.class); + http.addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class); + /* 로그아웃 필터 등록하기 LogoutHandler[] handlers = { @@ -114,26 +126,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ); */ - /* - oauth2 인증은 현재 해제해놨습니다 -> 차후 code를 front에서 어떤 경로로 받을 것인지 - 아니면 kakao에서 바로 redirect를 백엔드로 할 지 정해지면 - processing url 작성하겠습니다 - */ - http - .oauth2Login((oauth2) -> oauth2 - .userInfoEndpoint((userInfoEndpointConfig) -> userInfoEndpointConfig - .userService(oAuth2UserDetailServiceImpl)) - .successHandler(customSuccessHandler) - ); - - http.oauth2Login(AbstractHttpConfigurer::disable); - - CustomUsernamePasswordAuthenticationFilter customUsernameFilter = - new CustomUsernamePasswordAuthenticationFilter(authenticationManager(authenticationConfiguration), jwtUtil); - customUsernameFilter.setFilterProcessesUrl("/api/login"); - - http.addFilterAt(customUsernameFilter, UsernamePasswordAuthenticationFilter.class); - http.addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class); //http.addFilterAfter(customLogoutFilter, JwtAuthenticationFilter.class); return http.build(); diff --git a/src/main/java/poomasi/domain/auth/security/AuthTestService.java b/src/main/java/poomasi/domain/auth/security/AuthTestService.java index 1a6e21be..7a237b38 100644 --- a/src/main/java/poomasi/domain/auth/security/AuthTestService.java +++ b/src/main/java/poomasi/domain/auth/security/AuthTestService.java @@ -11,6 +11,8 @@ @Service public class AuthTestService { + //제가 테스트하려고 만든 건데 다음 pr때 지우겠습니다 + public String Test(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object impl = authentication.getPrincipal(); diff --git a/src/main/java/poomasi/domain/auth/security/handler/CustomSuccessHandler.java b/src/main/java/poomasi/domain/auth/security/handler/CustomSuccessHandler.java index 829e00bd..c4f0c483 100644 --- a/src/main/java/poomasi/domain/auth/security/handler/CustomSuccessHandler.java +++ b/src/main/java/poomasi/domain/auth/security/handler/CustomSuccessHandler.java @@ -11,31 +11,51 @@ import jakarta.servlet.http.HttpServletResponse; import jdk.jfr.Description; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import poomasi.domain.auth.security.userdetail.UserDetailsImpl; +import poomasi.domain.auth.token.util.JwtUtil; +import poomasi.domain.member.entity.Member; import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; @Slf4j @Component -public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { +public class CustomSuccessHandler implements AuthenticationSuccessHandler { + + private final JwtUtil jwtUtil; + + public CustomSuccessHandler(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } @Description("TODO : Oauth2.0 로그인이 성공하면 server access, refresh token을 발급하는 메서드") @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + log.info("[Oauth2 success handler] - OAuth2 로그인 성공적으로 완료되었습니다. access/refresh token 발급합니다."); + + UserDetailsImpl userDetailsImpl = (UserDetailsImpl) authentication.getPrincipal(); + Member member = userDetailsImpl.getMember(); + Long memberId = member.getId(); + + String accessToken = jwtUtil.generateAccessTokenById(memberId); + String refreshToken = jwtUtil.generateRefreshTokenById(memberId); + + response.addCookie(createCookie("refresh", refreshToken)); + response.addCookie(createCookie("access", accessToken)); - // 로직은 완성되었습니다 ~ - // Oauth2.0 로그인이 성공하면 server access, refresh token을 발급하는 메서드 - // - log.info("Oauth2 success handler."); - response.setHeader("access", ""); - response.addCookie(createCookie("refresh", "")); response.setStatus(HttpStatus.OK.value()); + response.getWriter(); } private Cookie createCookie(String key, String value) { diff --git a/src/main/java/poomasi/domain/auth/security/userdetail/OAuth2UserDetailServiceImpl.java b/src/main/java/poomasi/domain/auth/security/userdetail/OAuth2UserDetailServiceImpl.java index a595b326..e3923435 100644 --- a/src/main/java/poomasi/domain/auth/security/userdetail/OAuth2UserDetailServiceImpl.java +++ b/src/main/java/poomasi/domain/auth/security/userdetail/OAuth2UserDetailServiceImpl.java @@ -18,7 +18,6 @@ import java.util.Map; @Service -@Description("소셜 서비스와 로컬 계정 연동 할 것이라면 여기서 연동 해야 함") @Slf4j public class OAuth2UserDetailServiceImpl extends DefaultOAuth2UserService { @@ -44,33 +43,29 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } else{ log.warn("지원하지 않은 로그인 서비스 입니다."); } - + + // 정보 추출 String providerId = oAuth2UserInfo.getProviderId(); String email = oAuth2UserInfo.getEmail(); Role role = Role.ROLE_CUSTOMER; LoginType loginType = oAuth2UserInfo.getLoginType(); - - - //일단 없으면 가입시키는 쪽으로 구현ㄴ + + // 카카오 로그인을 처음 한 상태라면 회원가입 Member member = memberRepository.findByEmailAndDeletedAtIsNull(email).orElse(null); if(member == null) { member = Member.builder() .email(email) .role(role) - .loginType(loginType) // loginType에 맞게 변경 + .loginType(loginType) .provideId(providerId) .memberProfile(new MemberProfile()) .build(); memberRepository.save(member); - } //있다면 그냥 member 등록하기 - if(member.getLoginType()==LoginType.LOCAL){ - //member.setProviderId(providerId); -> 로그인 시 Id 조회함 - } // 카카오 회원으로 로그인이 되어 있다면 -> context에 저장 return new UserDetailsImpl(member, oAuth2User.getAttributes()); diff --git a/src/main/java/poomasi/domain/auth/security/userdetail/UserDetailsImpl.java b/src/main/java/poomasi/domain/auth/security/userdetail/UserDetailsImpl.java index 629e425d..ca9e5499 100644 --- a/src/main/java/poomasi/domain/auth/security/userdetail/UserDetailsImpl.java +++ b/src/main/java/poomasi/domain/auth/security/userdetail/UserDetailsImpl.java @@ -25,7 +25,7 @@ public UserDetailsImpl(Member member) { this.member = member; } - public UserDetailsImpl(Member member, Map attributes ) { + public UserDetailsImpl(Member member, Map attributes){ this.member = member; this.attributes = attributes; } @@ -75,7 +75,6 @@ public boolean isCredentialsNonExpired() { return true; } - //Oauth2 member name @Override public String getName() { return null; diff --git a/src/main/java/poomasi/domain/auth/token/util/JwtUtil.java b/src/main/java/poomasi/domain/auth/token/util/JwtUtil.java index 46501256..397046d6 100644 --- a/src/main/java/poomasi/domain/auth/token/util/JwtUtil.java +++ b/src/main/java/poomasi/domain/auth/token/util/JwtUtil.java @@ -91,8 +91,6 @@ public String getEmailFromTokenInFilter(final String token) { return getClaimFromToken(token, "email", String.class); } - // <--------------------------------------------> - // 토큰 생성 public String generateAccessTokenById(final Long memberId) { Map claims = createClaims(memberId); diff --git a/src/main/java/poomasi/domain/farm/controller/FarmController.java b/src/main/java/poomasi/domain/farm/controller/FarmController.java index cb3c81af..f1763f5f 100644 --- a/src/main/java/poomasi/domain/farm/controller/FarmController.java +++ b/src/main/java/poomasi/domain/farm/controller/FarmController.java @@ -25,4 +25,9 @@ public ResponseEntity getFarm(@PathVariable Long farmId) { public ResponseEntity getFarmList(Pageable pageable) { return ResponseEntity.ok(farmPlatformService.getFarmList(pageable)); } + + @GetMapping("byFarmer/{farmerId}") + public ResponseEntity getFarmsByFarmerId(@PathVariable Long farmerId) { + return ResponseEntity.ok(farmPlatformService.getFarmsByFarmerId(farmerId)); + } } diff --git a/src/main/java/poomasi/domain/farm/service/FarmPlatformService.java b/src/main/java/poomasi/domain/farm/service/FarmPlatformService.java index 54032ecf..2ace44df 100644 --- a/src/main/java/poomasi/domain/farm/service/FarmPlatformService.java +++ b/src/main/java/poomasi/domain/farm/service/FarmPlatformService.java @@ -22,4 +22,10 @@ public List getFarmList(Pageable pageable) { .map(FarmResponse::fromEntity) .collect(Collectors.toList()); } + + public List getFarmsByFarmerId(Long farmerId) { + return farmService.getFarmListByOwnerId(farmerId).stream() + .map(FarmResponse::fromEntity) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteFactory.java b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteFactory.java new file mode 100644 index 00000000..821b7353 --- /dev/null +++ b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteFactory.java @@ -0,0 +1,25 @@ +package poomasi.domain.image.deleteLinker; + +import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.ImageType; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class ImageDeleteFactory { + + private final Map handlerMap; + + public ImageDeleteFactory( + ProductDeleteLinker productDeleteLinker, + MemberProfileDeleteLinker memberProfileDeleteLinker) { + this.handlerMap = new HashMap<>(); + handlerMap.put(ImageType.PRODUCT, productDeleteLinker); + handlerMap.put(ImageType.MEMBER_PROFILE, memberProfileDeleteLinker); + } + + public ImageDeleteLinker getDeleteLinker(ImageType type) { + return handlerMap.get(type); + } +} diff --git a/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteLinker.java b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteLinker.java new file mode 100644 index 00000000..c5820c90 --- /dev/null +++ b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteLinker.java @@ -0,0 +1,7 @@ +package poomasi.domain.image.deleteLinker; + +import poomasi.domain.image.entity.Image; + +public interface ImageDeleteLinker { + void handleImageDeletion(Image image); +} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/deleteLinker/MemberProfileDeleteLinker.java b/src/main/java/poomasi/domain/image/deleteLinker/MemberProfileDeleteLinker.java new file mode 100644 index 00000000..696bed0d --- /dev/null +++ b/src/main/java/poomasi/domain/image/deleteLinker/MemberProfileDeleteLinker.java @@ -0,0 +1,24 @@ +package poomasi.domain.image.deleteLinker; + +import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.Image; +import poomasi.domain.member._profile.entity.MemberProfile; +import poomasi.domain.member._profile.service.MemberProfileService; + +@Component +public class MemberProfileDeleteLinker implements ImageDeleteLinker { + + private final MemberProfileService memberProfileService; + + public MemberProfileDeleteLinker(MemberProfileService memberProfileService) { + this.memberProfileService = memberProfileService; + } + + @Override + public void handleImageDeletion(Image image) { + MemberProfile memberProfile = memberProfileService.getMemberProfileById(image.getReferenceId()); + memberProfile.setProfileImage(null); + memberProfileService.saveMemberProfile(memberProfile); + } +} + diff --git a/src/main/java/poomasi/domain/image/deleteLinker/ProductDeleteLinker.java b/src/main/java/poomasi/domain/image/deleteLinker/ProductDeleteLinker.java new file mode 100644 index 00000000..9453cbce --- /dev/null +++ b/src/main/java/poomasi/domain/image/deleteLinker/ProductDeleteLinker.java @@ -0,0 +1,23 @@ +package poomasi.domain.image.deleteLinker; + +import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.Image; +import poomasi.domain.product.entity.Product; +import poomasi.domain.product.service.ProductService; + +@Component +public class ProductDeleteLinker implements ImageDeleteLinker { + + private final ProductService productService; + + public ProductDeleteLinker(ProductService productService) { + this.productService = productService; + } + + @Override + public void handleImageDeletion(Image image) { + Product product = productService.findProductById(image.getReferenceId()); + product.setImageUrl(null); + productService.saveExistedProduct(product); + } +} diff --git a/src/main/java/poomasi/domain/image/entity/Image.java b/src/main/java/poomasi/domain/image/entity/Image.java index dba3541d..38d70ebb 100644 --- a/src/main/java/poomasi/domain/image/entity/Image.java +++ b/src/main/java/poomasi/domain/image/entity/Image.java @@ -6,11 +6,10 @@ import poomasi.domain.image.dto.ImageRequest; import java.time.LocalDateTime; -import java.util.Date; @Entity @Table(name = "image", uniqueConstraints = { - @UniqueConstraint(columnNames = {"type", "reference_id", "object_key"}) + @UniqueConstraint(columnNames = {"type", "reference_id"}) }) @Getter @Setter @@ -22,10 +21,10 @@ public class Image { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) + @Column(nullable = false, unique = true) private String objectKey; - @Column(nullable = false) + @Column(nullable = false, unique = true) private String imageUrl; @Enumerated(EnumType.STRING) @@ -37,7 +36,7 @@ public class Image { @Column(name = "created_at", nullable = false) @Temporal(TemporalType.TIMESTAMP) - private Date createdAt = new Date(); + private LocalDateTime createdAt = LocalDateTime.now(); @Column(name = "deleted_at") private LocalDateTime deletedAt; diff --git a/src/main/java/poomasi/domain/image/linker/ImageLinker.java b/src/main/java/poomasi/domain/image/linker/ImageLinker.java new file mode 100644 index 00000000..ab0fa128 --- /dev/null +++ b/src/main/java/poomasi/domain/image/linker/ImageLinker.java @@ -0,0 +1,9 @@ +package poomasi.domain.image.linker; + +import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; + +public interface ImageLinker { + boolean supports(ImageType type); + void link(Long referenceId, Image savedImage); +} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/linker/ImageLinkerFactory.java b/src/main/java/poomasi/domain/image/linker/ImageLinkerFactory.java new file mode 100644 index 00000000..1064266e --- /dev/null +++ b/src/main/java/poomasi/domain/image/linker/ImageLinkerFactory.java @@ -0,0 +1,23 @@ +package poomasi.domain.image.linker; + +import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.ImageType; + +import java.util.List; + +@Component +public class ImageLinkerFactory { + + private final List linkers; + + public ImageLinkerFactory(List linkers) { + this.linkers = linkers; + } + + public ImageLinker getLinker(ImageType type) { + return linkers.stream() + .filter(linker -> linker.supports(type)) + .findFirst() + .orElse(null); + } +} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/linker/MemberProfileImageLinker.java b/src/main/java/poomasi/domain/image/linker/MemberProfileImageLinker.java new file mode 100644 index 00000000..3008f725 --- /dev/null +++ b/src/main/java/poomasi/domain/image/linker/MemberProfileImageLinker.java @@ -0,0 +1,31 @@ +package poomasi.domain.image.linker; + +import org.springframework.stereotype.Service; +import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; +import poomasi.domain.member._profile.entity.MemberProfile; +import poomasi.domain.member._profile.service.MemberProfileService; + +@Service +public class MemberProfileImageLinker implements ImageLinker { + + private final MemberProfileService memberProfileService; + + public MemberProfileImageLinker(MemberProfileService memberProfileService) { + this.memberProfileService = memberProfileService; + } + + @Override + public boolean supports(ImageType type) { + return type == ImageType.MEMBER_PROFILE; + } + + @Override + public void link(Long referenceId, Image savedImage) { + MemberProfile memberProfile = memberProfileService.getMemberProfileById(referenceId); + memberProfile.setProfileImage(savedImage); + memberProfileService.saveMemberProfile(memberProfile); + } +} + + diff --git a/src/main/java/poomasi/domain/image/linker/ProductImageLinker.java b/src/main/java/poomasi/domain/image/linker/ProductImageLinker.java new file mode 100644 index 00000000..fc75dac6 --- /dev/null +++ b/src/main/java/poomasi/domain/image/linker/ProductImageLinker.java @@ -0,0 +1,29 @@ +package poomasi.domain.image.linker; + +import org.springframework.stereotype.Service; +import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; +import poomasi.domain.product.entity.Product; +import poomasi.domain.product.service.ProductService; + +@Service +public class ProductImageLinker implements ImageLinker { + + private final ProductService productService; + + public ProductImageLinker(ProductService productService) { + this.productService = productService; + } + + @Override + public boolean supports(ImageType type) { + return type == ImageType.PRODUCT; + } + + @Override + public void link(Long referenceId, Image savedImage) { + Product product = productService.findProductById(referenceId); + product.setImageUrl(savedImage.getImageUrl()); + productService.saveExistedProduct(product); + } +} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/service/ImageService.java b/src/main/java/poomasi/domain/image/service/ImageService.java index cf72c618..b0cabc50 100644 --- a/src/main/java/poomasi/domain/image/service/ImageService.java +++ b/src/main/java/poomasi/domain/image/service/ImageService.java @@ -3,19 +3,21 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import poomasi.domain.image.deleteLinker.ImageDeleteFactory; +import poomasi.domain.image.deleteLinker.ImageDeleteLinker; import poomasi.domain.image.dto.ImageRequest; import poomasi.domain.image.entity.Image; import poomasi.domain.image.entity.ImageType; +import poomasi.domain.image.linker.ImageLinker; +import poomasi.domain.image.linker.ImageLinkerFactory; import poomasi.domain.image.repository.ImageRepository; -import poomasi.domain.image.validation.ImageOwnerValidator; -import poomasi.domain.image.validation.ImageOwnerValidatorFactory; -import poomasi.domain.member._profile.entity.MemberProfile; -import poomasi.domain.member._profile.service.MemberProfileService; +import poomasi.domain.image.validator.ImageOwnerValidator; +import poomasi.domain.image.validator.ImageOwnerValidatorFactory; import poomasi.domain.member.entity.Member; -import poomasi.domain.member.repository.MemberRepository; +import poomasi.domain.member.service.MemberService; import poomasi.global.error.BusinessException; -import java.util.Date; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -28,13 +30,15 @@ public class ImageService { private static final int DEFAULT_IMAGE_LIMIT = 5; - private static final int MEMBER_PROFILE_IMAGE_LIMIT = 1; + private static final int IMAGE_ONE_LIMIT = 1; private final ImageRepository imageRepository; + private final MemberService memberService; private final ImageOwnerValidatorFactory validatorFactory; - private final MemberRepository memberRepository; - private final MemberProfileService memberProfileService; + private final ImageLinkerFactory imageLinkerFactory; + private final ImageDeleteFactory imageDeleteFactory; + // 이미지 타입에 맞게 link, deleteLink, 개수 제한, ownerValidate @Transactional public Image saveImage(Long memberId, ImageRequest imageRequest) { @@ -46,9 +50,7 @@ public Image saveImage(Long memberId, ImageRequest imageRequest) { .map(existingImage -> recoverImageOrThrow(existingImage, imageRequest)) .orElseGet(() -> imageRequest.toEntity(imageRequest)); - if (imageRequest.type() == ImageType.MEMBER_PROFILE) { - linkImageToMemberProfile(imageRequest.referenceId(), image); - } + imageLink(image); return imageRepository.save(image); } @@ -66,8 +68,7 @@ private void validateImageOwner(Long memberId, ImageType type, Long referenceId) } private boolean isAdmin(Long memberId) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); + Member member = memberService.findMemberById(memberId); return member.isAdmin(); } @@ -81,15 +82,15 @@ private Image recoverImageOrThrow(Image existingImage, ImageRequest imageRequest throw new BusinessException(IMAGE_ALREADY_EXISTS); } existingImage.setDeletedAt(null); - existingImage.setCreatedAt(new Date()); + existingImage.setCreatedAt(LocalDateTime.now()); existingImage.update(imageRequest); return existingImage; } private void validateImageLimit(ImageRequest imageRequest) { int imageLimit = DEFAULT_IMAGE_LIMIT; - if (imageRequest.type() == ImageType.MEMBER_PROFILE) { - imageLimit = MEMBER_PROFILE_IMAGE_LIMIT; // 멤버 프로필 이미지는 한 장으로 제한 + if (imageRequest.type() == ImageType.MEMBER_PROFILE || imageRequest.type() == ImageType.PRODUCT) { + imageLimit = IMAGE_ONE_LIMIT; // 멤버 프로필, 상품 이미지는 한 장으로 제한 } if (imageRepository.countByTypeAndReferenceIdAndDeletedAtIsNull(imageRequest.type(), imageRequest.referenceId()) >= imageLimit) { @@ -97,12 +98,6 @@ private void validateImageLimit(ImageRequest imageRequest) { } } - private void linkImageToMemberProfile(Long referenceId, Image savedImage) { - MemberProfile memberProfile = memberProfileService.getMemberProfileById(referenceId); - memberProfile.setProfileImage(savedImage); - memberProfileService.saveMemberProfile(memberProfile); - } - // 여러 이미지 저장 @Transactional public List saveMultipleImages(Long memberId, List imageRequests) { @@ -115,7 +110,9 @@ public List saveMultipleImages(Long memberId, List imageReq public void deleteImage(Long memberId, Long id) { Image image = getImageById(id); validateImageOwner(memberId, image.getType(), image.getReferenceId()); - imageRepository.deleteById(id); + imageRepository.delete(image); + + imageDeleteLink(image); } public Image getImageById(Long id) { @@ -138,8 +135,17 @@ public Image updateImage(Long memberId, Long id, ImageRequest imageRequest) { validateImageLimit(imageRequest); } + if (!image.getType().equals(imageRequest.type())) { + imageDeleteLink(image); + } + image.update(imageRequest); + if (!image.getType().equals(imageRequest.type())) { + imageLink(image); + } + + return imageRepository.save(image); } @@ -155,7 +161,27 @@ public void recoverImage(Long memberId, Long id) { validateImageLimit(image.toRequest(image)); image.setDeletedAt(null); + + imageLink(image); + imageRepository.save(image); } + // 이미지와 해당 이미지를 가지는 엔티티 연결 + private void imageLink(Image image){ + ImageLinker linker = imageLinkerFactory.getLinker(image.getType()); + if (linker != null){ + linker.link(image.getReferenceId(), image); + } + } + + // 이미지 삭제 시 해당 이미지를 가지는 엔티티에서도 처리 + private void imageDeleteLink(Image image){ + ImageDeleteLinker imageDeleteLinker = imageDeleteFactory.getDeleteLinker(image.getType()); + if (imageDeleteLinker != null) { + imageDeleteLinker.handleImageDeletion(image); // 해당 타입에 맞는 삭제 처리 + } + } + + } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/validation/MemberProfileOwnerValidator.java b/src/main/java/poomasi/domain/image/validation/MemberProfileOwnerValidator.java deleted file mode 100644 index b597d5b0..00000000 --- a/src/main/java/poomasi/domain/image/validation/MemberProfileOwnerValidator.java +++ /dev/null @@ -1,18 +0,0 @@ -package poomasi.domain.image.validation; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import poomasi.domain.member._profile.repository.MemberProfileRepository; - -@Component -@RequiredArgsConstructor -public class MemberProfileOwnerValidator implements ImageOwnerValidator{ - private final MemberProfileRepository memberProfileRepository; - - @Override - public boolean validateOwner(Long memberId, Long referenceId) { - return memberProfileRepository.findById(referenceId) - .filter(memberProfile -> memberProfile.getMember().getId().equals(memberId)) - .isPresent(); - } -} diff --git a/src/main/java/poomasi/domain/image/validation/FarmOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/FarmOwnerValidator.java similarity index 92% rename from src/main/java/poomasi/domain/image/validation/FarmOwnerValidator.java rename to src/main/java/poomasi/domain/image/validator/FarmOwnerValidator.java index ca376272..193aa1a0 100644 --- a/src/main/java/poomasi/domain/image/validation/FarmOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/FarmOwnerValidator.java @@ -1,4 +1,4 @@ -package poomasi.domain.image.validation; +package poomasi.domain.image.validator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/poomasi/domain/image/validation/ImageOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidator.java similarity index 71% rename from src/main/java/poomasi/domain/image/validation/ImageOwnerValidator.java rename to src/main/java/poomasi/domain/image/validator/ImageOwnerValidator.java index 8b51556f..1faa853f 100644 --- a/src/main/java/poomasi/domain/image/validation/ImageOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidator.java @@ -1,4 +1,4 @@ -package poomasi.domain.image.validation; +package poomasi.domain.image.validator; public interface ImageOwnerValidator { boolean validateOwner(Long memberId, Long referenceId); diff --git a/src/main/java/poomasi/domain/image/validation/ImageOwnerValidatorFactory.java b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidatorFactory.java similarity index 96% rename from src/main/java/poomasi/domain/image/validation/ImageOwnerValidatorFactory.java rename to src/main/java/poomasi/domain/image/validator/ImageOwnerValidatorFactory.java index d22141b2..2de7c789 100644 --- a/src/main/java/poomasi/domain/image/validation/ImageOwnerValidatorFactory.java +++ b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidatorFactory.java @@ -1,4 +1,4 @@ -package poomasi.domain.image.validation; +package poomasi.domain.image.validator; import org.springframework.stereotype.Component; import poomasi.domain.image.entity.ImageType; diff --git a/src/main/java/poomasi/domain/image/validator/MemberProfileOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/MemberProfileOwnerValidator.java new file mode 100644 index 00000000..f49c12ea --- /dev/null +++ b/src/main/java/poomasi/domain/image/validator/MemberProfileOwnerValidator.java @@ -0,0 +1,18 @@ +package poomasi.domain.image.validator; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import poomasi.domain.member.repository.MemberRepository; + +@Component +@RequiredArgsConstructor +public class MemberProfileOwnerValidator implements ImageOwnerValidator{ + private final MemberRepository memberRepository; + + @Override + public boolean validateOwner(Long memberId, Long referenceId) { + return memberRepository.findById(memberId) + .filter(member -> member.getMemberProfile().getId().equals(referenceId)) + .isPresent(); + } +} diff --git a/src/main/java/poomasi/domain/image/validation/ProductOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/ProductOwnerValidator.java similarity index 93% rename from src/main/java/poomasi/domain/image/validation/ProductOwnerValidator.java rename to src/main/java/poomasi/domain/image/validator/ProductOwnerValidator.java index 6afc0bfb..43175fa2 100644 --- a/src/main/java/poomasi/domain/image/validation/ProductOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/ProductOwnerValidator.java @@ -1,4 +1,4 @@ -package poomasi.domain.image.validation; +package poomasi.domain.image.validator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/poomasi/domain/image/validation/ReviewOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/ReviewOwnerValidator.java similarity index 93% rename from src/main/java/poomasi/domain/image/validation/ReviewOwnerValidator.java rename to src/main/java/poomasi/domain/image/validator/ReviewOwnerValidator.java index 57073e2d..5f8d8d6b 100644 --- a/src/main/java/poomasi/domain/image/validation/ReviewOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/ReviewOwnerValidator.java @@ -1,4 +1,4 @@ -package poomasi.domain.image.validation; +package poomasi.domain.image.validator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/poomasi/domain/member/_profile/controller/MemberProfileController.java b/src/main/java/poomasi/domain/member/_profile/controller/MemberProfileController.java deleted file mode 100644 index 97de1cdf..00000000 --- a/src/main/java/poomasi/domain/member/_profile/controller/MemberProfileController.java +++ /dev/null @@ -1,14 +0,0 @@ -package poomasi.domain.member._profile.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import poomasi.domain.member._profile.service.MemberProfileService; - -@RestController -@RequiredArgsConstructor -@RequestMapping("api/member/profile") -public class MemberProfileController { - - private final MemberProfileService memberProfileService; -} diff --git a/src/main/java/poomasi/domain/member/_profile/dto/request/AddressUpdateRequest.java b/src/main/java/poomasi/domain/member/_profile/dto/request/AddressUpdateRequest.java new file mode 100644 index 00000000..8a8ac1d0 --- /dev/null +++ b/src/main/java/poomasi/domain/member/_profile/dto/request/AddressUpdateRequest.java @@ -0,0 +1,8 @@ +package poomasi.domain.member._profile.dto.request; + +public record AddressUpdateRequest( + String defaultAddress, + String addressDetail, + Long coordinateX, + Long coordinateY) { +} diff --git a/src/main/java/poomasi/domain/member/_profile/dto/request/MemberProfileRequest.java b/src/main/java/poomasi/domain/member/_profile/dto/request/MemberProfileRequest.java deleted file mode 100644 index 3492e58d..00000000 --- a/src/main/java/poomasi/domain/member/_profile/dto/request/MemberProfileRequest.java +++ /dev/null @@ -1,5 +0,0 @@ -package poomasi.domain.member._profile.dto.request; - - -public record MemberProfileRequest() { -} diff --git a/src/main/java/poomasi/domain/member/_profile/dto/response/CommonProfileResponse.java b/src/main/java/poomasi/domain/member/_profile/dto/response/CommonProfileResponse.java deleted file mode 100644 index cb665655..00000000 --- a/src/main/java/poomasi/domain/member/_profile/dto/response/CommonProfileResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -package poomasi.domain.member._profile.dto.response; - -import lombok.AllArgsConstructor; -import poomasi.domain.image.entity.Image; -import poomasi.domain.member._profile.entity.MemberProfile; - -import java.time.LocalDateTime; - -@AllArgsConstructor -public class CommonProfileResponse implements MemberProfileResponse { - String phoneNumber; - boolean isBanned; - LocalDateTime createdAt; - Image profileImage; - - public static CommonProfileResponse fromEntity(MemberProfile profile) { - return new CommonProfileResponse( - profile.getPhoneNumber(), - profile.isBanned(), - profile.getCreatedAt(), - profile.getProfileImage() - ); - } - -} diff --git a/src/main/java/poomasi/domain/member/_profile/dto/response/CustomerProfileResponse.java b/src/main/java/poomasi/domain/member/_profile/dto/response/CustomerProfileResponse.java deleted file mode 100644 index 53501e03..00000000 --- a/src/main/java/poomasi/domain/member/_profile/dto/response/CustomerProfileResponse.java +++ /dev/null @@ -1,28 +0,0 @@ -package poomasi.domain.member._profile.dto.response; - -import lombok.AllArgsConstructor; -import poomasi.domain.image.entity.Image; -import poomasi.domain.member._profile.entity.CustomerProfile; - -import java.time.LocalDateTime; - -@AllArgsConstructor -public class CustomerProfileResponse implements MemberProfileResponse{ - String phoneNumber; - String address; - String addressDetail; - boolean isBanned; - LocalDateTime createdAt; - Image profileImage; - - public static CustomerProfileResponse fromEntity(CustomerProfile profile) { - return new CustomerProfileResponse( - profile.getPhoneNumber(), - profile.getAddress(), - profile.getAddressDetail(), - profile.isBanned(), - profile.getCreatedAt(), - profile.getProfileImage() - ); - } -} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/member/_profile/dto/response/FarmerProfileResponse.java b/src/main/java/poomasi/domain/member/_profile/dto/response/FarmerProfileResponse.java index 53fed2f5..ec11888f 100644 --- a/src/main/java/poomasi/domain/member/_profile/dto/response/FarmerProfileResponse.java +++ b/src/main/java/poomasi/domain/member/_profile/dto/response/FarmerProfileResponse.java @@ -1,39 +1,22 @@ package poomasi.domain.member._profile.dto.response; -import lombok.AllArgsConstructor; import poomasi.domain.image.entity.Image; -import poomasi.domain.member._profile.entity.FarmerProfile; +import poomasi.domain.member._profile.entity.MemberProfile; import java.time.LocalDateTime; -import java.util.List; -@AllArgsConstructor -public class FarmerProfileResponse implements MemberProfileResponse { - String phoneNumber; - boolean isBanned; - LocalDateTime createdAt; - Image profileImage; - String storeName; - String farmName; - List businessRegistrationNumbers; - String storeAddress; - String storeAddressDetail; - String farmAddress; - String farmAddressDetail; - - public static FarmerProfileResponse fromEntity(FarmerProfile profile) { +public record FarmerProfileResponse( + String phoneNumber, + boolean isBanned, + LocalDateTime createdAt, + Image profileImage +){ + public static FarmerProfileResponse fromEntity(MemberProfile profile) { return new FarmerProfileResponse( profile.getPhoneNumber(), profile.isBanned(), profile.getCreatedAt(), - profile.getProfileImage(), - profile.getStoreName(), - profile.getFarmName(), - profile.getBusinessRegistrationNumbers(), - profile.getStoreAddress(), - profile.getStoreAddressDetail(), - profile.getFarmAddress(), - profile.getFarmAddressDetail() + profile.getProfileImage() ); } } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/member/_profile/dto/response/MemberProfileResponse.java b/src/main/java/poomasi/domain/member/_profile/dto/response/MemberProfileResponse.java index b191dda0..691d8673 100644 --- a/src/main/java/poomasi/domain/member/_profile/dto/response/MemberProfileResponse.java +++ b/src/main/java/poomasi/domain/member/_profile/dto/response/MemberProfileResponse.java @@ -1,18 +1,31 @@ package poomasi.domain.member._profile.dto.response; -import poomasi.domain.member._profile.entity.CustomerProfile; -import poomasi.domain.member._profile.entity.FarmerProfile; +import poomasi.domain.image.entity.Image; import poomasi.domain.member._profile.entity.MemberProfile; -public interface MemberProfileResponse { +import java.time.LocalDateTime; - static MemberProfileResponse fromEntity(MemberProfile profile) { - if (profile instanceof FarmerProfile farmerProfile) { - return FarmerProfileResponse.fromEntity(farmerProfile); - } else if (profile instanceof CustomerProfile customerProfile) { - return CustomerProfileResponse.fromEntity(customerProfile); - } else { - return CommonProfileResponse.fromEntity(profile); - } +public record MemberProfileResponse( + String phoneNumber, + String defaultAddress, + String addressDetail, + Long coordinateX, + Long coordinateY, + boolean isBanned, + LocalDateTime createdAt, + Image profileImage){ + + + public static MemberProfileResponse fromEntity(MemberProfile profile) { + return new MemberProfileResponse( + profile.getPhoneNumber(), + profile.getDefaultAddress(), + profile.getAddressDetail(), + profile.getCoordinateX(), + profile.getCoordinateY(), + profile.isBanned(), + profile.getCreatedAt(), + profile.getProfileImage() + ); } } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/member/_profile/entity/CustomerProfile.java b/src/main/java/poomasi/domain/member/_profile/entity/CustomerProfile.java deleted file mode 100644 index ea7b83ad..00000000 --- a/src/main/java/poomasi/domain/member/_profile/entity/CustomerProfile.java +++ /dev/null @@ -1,23 +0,0 @@ -package poomasi.domain.member._profile.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import lombok.Getter; - -@Entity -@Getter -@DiscriminatorValue("CUSTOMER") -public class CustomerProfile extends MemberProfile{ - @Column(nullable = true, length = 255) - private String address; - - @Column(nullable = true, length = 255) - private String addressDetail; - - @Column(nullable=true, length=255) - private Long coordinateX; - - @Column(nullable=true, length=255) - private Long coordinateY; -} diff --git a/src/main/java/poomasi/domain/member/_profile/entity/FarmerProfile.java b/src/main/java/poomasi/domain/member/_profile/entity/FarmerProfile.java deleted file mode 100644 index 06b946c7..00000000 --- a/src/main/java/poomasi/domain/member/_profile/entity/FarmerProfile.java +++ /dev/null @@ -1,47 +0,0 @@ -package poomasi.domain.member._profile.entity; - -import jakarta.persistence.*; -import lombok.Getter; - -import java.util.List; - -@Entity -@Getter -@DiscriminatorValue("FARMER") -public class FarmerProfile extends MemberProfile{ - @Column(nullable = true, length = 100, unique = true) - private String storeName; - - @Column(nullable = true, length = 100) - private String storeAddress; - - @Column(nullable = true, length = 100) - private String storeAddressDetail; - - @Column(nullable=true, length=100) - private Long storeCoordinateX; - - @Column(nullable=true, length=100) - private Long storeCoordinateY; - - @Column(nullable = true, length = 100, unique = true) - private String farmName; - - @Column(nullable = true, length = 100) - private String farmAddress; - - @Column(nullable = true, length = 100) - private String farmAddressDetail; - - @Column(nullable=true, length=100) - private Long farmCoordinateX; - - @Column(nullable=true, length=100) - private Long farmCoordinateY; - - @ElementCollection - @CollectionTable(name = "business_registration_numbers", joinColumns = @JoinColumn(name = "farmer_profile_id")) - @Column(nullable = true, length=255) - private List businessRegistrationNumbers; - -} diff --git a/src/main/java/poomasi/domain/member/_profile/entity/MemberProfile.java b/src/main/java/poomasi/domain/member/_profile/entity/MemberProfile.java index af2b5849..05a969c1 100644 --- a/src/main/java/poomasi/domain/member/_profile/entity/MemberProfile.java +++ b/src/main/java/poomasi/domain/member/_profile/entity/MemberProfile.java @@ -2,8 +2,8 @@ import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.SQLDelete; import poomasi.domain.image.entity.Image; -import poomasi.domain.member.entity.Member; import java.time.LocalDateTime; @@ -12,14 +12,14 @@ @Table(name = "member_profile") @AllArgsConstructor @Builder -@Inheritance(strategy = InheritanceType.JOINED) -@DiscriminatorColumn(name = "profile_type") +@SQLDelete(sql = "UPDATE member_profile SET deleted_at = current_timestamp WHERE id = ?") public class MemberProfile { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Setter @Column(nullable = true, length = 20) private String phoneNumber; @@ -30,21 +30,55 @@ public class MemberProfile { @Column(nullable = false) private LocalDateTime createdAt; + @Setter + @Column + private LocalDateTime deletedAt; + @Setter @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "profile_image_id") private Image profileImage; - @Setter - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", referencedColumnName = "id") - private Member member; + // 기본 배송지 + @Column(nullable = true, length = 255) + private String defaultAddress; + + @Column(nullable = true, length = 255) + private String addressDetail; + + @Column(nullable=true, length=255) + private Long coordinateX; + + @Column(nullable=true, length=255) + private Long coordinateY; @PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); } + @PreRemove + public void preRemove() { + // MemberProfile이 삭제되기 전에 연관된 이미지를 삭제 + if (profileImage != null) { + profileImage.setDeletedAt(LocalDateTime.now()); + } + } + public MemberProfile() { } + + public void setAddress( + String defaultAddress, + String addressDetail, + Long coordinateX, + Long coordinateY) { + if (defaultAddress != null) this.defaultAddress = defaultAddress; + if (addressDetail != null) this.addressDetail = addressDetail; + if (coordinateX != null) this.coordinateX = coordinateX; + if (coordinateY != null) this.coordinateY = coordinateY; + + } + + } diff --git a/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java b/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java index f59a2490..433f671c 100644 --- a/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java +++ b/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java @@ -1,19 +1,9 @@ package poomasi.domain.member._profile.repository; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import poomasi.domain.member._profile.entity.FarmerProfile; import poomasi.domain.member._profile.entity.MemberProfile; -import java.util.Optional; - @Repository public interface MemberProfileRepository extends JpaRepository { - - // FarmerProfile 타입만 조회 -// @Query("SELECT p FROM FarmerProfile p WHERE p.farmName = :farmName") -// Optional findFarmerByFarmName(@Param("farmName") String farmName); - } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/member/controller/MemberController.java b/src/main/java/poomasi/domain/member/controller/MemberController.java index 2ee754f3..19de271e 100644 --- a/src/main/java/poomasi/domain/member/controller/MemberController.java +++ b/src/main/java/poomasi/domain/member/controller/MemberController.java @@ -9,13 +9,13 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import poomasi.domain.auth.security.userdetail.UserDetailsImpl; -import poomasi.domain.member.dto.request.FarmerQualificationRequest; -import poomasi.domain.member.dto.response.MemberResponse; -import poomasi.domain.member.dto.response.MemberSummaryResponse; +import poomasi.domain.member._profile.dto.request.AddressUpdateRequest; +import poomasi.domain.member.dto.request.CustomerUpdateRequest; +import poomasi.domain.member.dto.request.FarmerUpdateRequest; +import poomasi.domain.member.dto.response.*; import poomasi.domain.member.entity.Member; import poomasi.domain.member.service.MemberService; import poomasi.domain.member.dto.request.SignupRequest; -import poomasi.domain.member.dto.response.SignUpResponse; @RestController @RequiredArgsConstructor @@ -32,10 +32,9 @@ public ResponseEntity signUp(@RequestBody SignupRequest signupRe @PutMapping("/toFarmer") @Secured("ROLE_CUSTOMER") - public ResponseEntity convertToFarmer(@AuthenticationPrincipal UserDetailsImpl userDetails, - @RequestBody FarmerQualificationRequest request) { + public ResponseEntity convertToFarmer(@AuthenticationPrincipal UserDetailsImpl userDetails) { Member member = userDetails.getMember(); - memberService.convertToFarmer(member, request.hasFarmerQualification()); + memberService.convertToFarmer(member); return ResponseEntity.noContent().build(); } @@ -75,5 +74,52 @@ public ResponseEntity getMemberSummaryById(@PathVariable return ResponseEntity.ok(memberSummaryResponse); } + @PutMapping("/customer/update") + @Secured("ROLE_CUSTOMER") + public ResponseEntity updateCustomer( + @AuthenticationPrincipal UserDetailsImpl userDetails, + @RequestBody CustomerUpdateRequest customerUpdateRequest) { + + Member member = userDetails.getMember(); + Member updatedMember = memberService.updateCustomer(member, customerUpdateRequest); + + MemberResponse memberResponse = MemberResponse.fromEntity(updatedMember); + return ResponseEntity.ok(memberResponse); + } + + @PutMapping("/farmer/update") + @Secured("ROLE_FARMER") + public ResponseEntity updateFarmer( + @AuthenticationPrincipal UserDetailsImpl userDetails, + @RequestBody FarmerUpdateRequest farmerUpdateRequest) { + + Member member = userDetails.getMember(); + Member updatedMember = memberService.updateFarmer(member, farmerUpdateRequest); + + FarmerResponse memberResponse = FarmerResponse.fromEntity(updatedMember); + return ResponseEntity.ok(memberResponse); + } + + @PutMapping("/customer/update/address") + @Secured("ROLE_CUSTOMER") + public ResponseEntity updateAddress( + @AuthenticationPrincipal UserDetailsImpl userDetails, + @RequestBody AddressUpdateRequest addressUpdateRequest + ) { + Member member = userDetails.getMember(); + memberService.updateAddress(member, addressUpdateRequest); + return ResponseEntity.ok().build(); + } + + // 회원 탈퇴, 복구, 금지 + // s3스케줄러 구현하긴해야함 + + // 이미지 validator 타입 추가 + + // 이미지 업로드 실패할시 처리 + + + + } diff --git a/src/main/java/poomasi/domain/member/dto/request/CustomerUpdateRequest.java b/src/main/java/poomasi/domain/member/dto/request/CustomerUpdateRequest.java new file mode 100644 index 00000000..693da308 --- /dev/null +++ b/src/main/java/poomasi/domain/member/dto/request/CustomerUpdateRequest.java @@ -0,0 +1,8 @@ +package poomasi.domain.member.dto.request; + +public record CustomerUpdateRequest( + String name, + String email, + String password, + String phoneNumber) { +} diff --git a/src/main/java/poomasi/domain/member/dto/request/FarmerQualificationRequest.java b/src/main/java/poomasi/domain/member/dto/request/FarmerQualificationRequest.java deleted file mode 100644 index d4e6147d..00000000 --- a/src/main/java/poomasi/domain/member/dto/request/FarmerQualificationRequest.java +++ /dev/null @@ -1,3 +0,0 @@ -package poomasi.domain.member.dto.request; - -public record FarmerQualificationRequest(Boolean hasFarmerQualification) {} diff --git a/src/main/java/poomasi/domain/member/dto/request/FarmerUpdateRequest.java b/src/main/java/poomasi/domain/member/dto/request/FarmerUpdateRequest.java new file mode 100644 index 00000000..3e16d19a --- /dev/null +++ b/src/main/java/poomasi/domain/member/dto/request/FarmerUpdateRequest.java @@ -0,0 +1,10 @@ +package poomasi.domain.member.dto.request; + +public record FarmerUpdateRequest( + String name, + String email, + String password, + String phoneNumber, + String storeName, + String storeAddress) { +} diff --git a/src/main/java/poomasi/domain/member/dto/response/FarmerResponse.java b/src/main/java/poomasi/domain/member/dto/response/FarmerResponse.java new file mode 100644 index 00000000..4544c1eb --- /dev/null +++ b/src/main/java/poomasi/domain/member/dto/response/FarmerResponse.java @@ -0,0 +1,22 @@ +package poomasi.domain.member.dto.response; + +import poomasi.domain.member._profile.dto.response.FarmerProfileResponse; +import poomasi.domain.member.entity.Member; + +public record FarmerResponse( + Long id, + String name, + String email, + String role, + FarmerProfileResponse memberProfile +) { + public static FarmerResponse fromEntity(Member member) { + return new FarmerResponse( + member.getId(), + member.getName(), + member.getEmail(), + member.getRole().name(), + member.getMemberProfile() != null ? FarmerProfileResponse.fromEntity(member.getMemberProfile()) : null + ); + } +} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/member/dto/response/MemberProfileResponse.java b/src/main/java/poomasi/domain/member/dto/response/MemberProfileResponse.java deleted file mode 100644 index 9a36d9e7..00000000 --- a/src/main/java/poomasi/domain/member/dto/response/MemberProfileResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -package poomasi.domain.member.dto.response; - -import poomasi.domain.member.entity.MemberProfile; - -import java.time.LocalDateTime; - -public record MemberProfileResponse( - String name, - String phoneNumber, - String address, - String addressDetail, - Long coordinateX, - Long coordinateY, - boolean isBanned, - LocalDateTime createdAt -) { - public static MemberProfileResponse fromEntity(MemberProfile profile) { - return new MemberProfileResponse( - profile.getName(), - profile.getPhoneNumber(), - profile.getAddress(), - profile.getAddressDetail(), - profile.getCoordinateX(), - profile.getCoordinateY(), - profile.isBanned(), - profile.getCreatedAt() - ); - } -} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/member/dto/response/MemberSummaryResponse.java b/src/main/java/poomasi/domain/member/dto/response/MemberSummaryResponse.java index f57c1bfa..e26fa705 100644 --- a/src/main/java/poomasi/domain/member/dto/response/MemberSummaryResponse.java +++ b/src/main/java/poomasi/domain/member/dto/response/MemberSummaryResponse.java @@ -7,7 +7,7 @@ public record MemberSummaryResponse(String name, Image profileImage) { public static MemberSummaryResponse fromEntity(Member member) { return new MemberSummaryResponse( member.getName(), - member.getMemberProfile().getProfileImage() + member.getMemberProfile() != null ? member.getMemberProfile().getProfileImage() : null ); } } diff --git a/src/main/java/poomasi/domain/member/entity/Member.java b/src/main/java/poomasi/domain/member/entity/Member.java index 2292127a..5849d00d 100644 --- a/src/main/java/poomasi/domain/member/entity/Member.java +++ b/src/main/java/poomasi/domain/member/entity/Member.java @@ -9,11 +9,7 @@ import org.hibernate.annotations.SQLDelete; import poomasi.domain.store.entity.Store; import poomasi.domain.member._profile.entity.MemberProfile; -import poomasi.domain.store.entity.Store; import poomasi.domain.order.entity._product.ProductOrder; -import poomasi.domain.store.entity.Store; -import poomasi.domain.member._profile.entity.MemberProfile; -import poomasi.domain.store.entity.Store; import poomasi.domain.wishlist.entity.WishList; import poomasi.global.error.BusinessError; import poomasi.global.error.BusinessException; @@ -30,12 +26,15 @@ public class Member { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Setter @Column(nullable = true, length = 50) private String name; + @Setter @Column(unique = true, nullable = true, length = 50) private String email; + @Setter @Column(nullable = true) private String password; @@ -52,7 +51,7 @@ public class Member { @Column(nullable = true) private String provideId; - @OneToOne(mappedBy = "member", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY) private MemberProfile memberProfile; @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @@ -70,7 +69,7 @@ public class Member { @Setter @OneToOne(mappedBy="owner", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - Store store; + private Store store; public Member(String name, String email, String password, LoginType loginType, Role role) { this.name = name; @@ -78,18 +77,7 @@ public Member(String name, String email, String password, LoginType loginType, R this.password = password; this.loginType = loginType; this.role = role; - } - - public Member(String email, Role role) { - this.email = email; - this.role = role; - } - - public void setMemberProfile(MemberProfile memberProfile) { - this.memberProfile = memberProfile; - if (memberProfile != null) { - memberProfile.setMember(this); - } + this.memberProfile = getOrCreateProfile(); } @Builder @@ -121,4 +109,21 @@ public Store getStore() { return store; } + public MemberProfile getOrCreateProfile() { + if (this.memberProfile == null) { + this.memberProfile = new MemberProfile(); + } + return memberProfile; + } + + public Store getOrCreateStore() { + if (this.store == null) { + this.store = new Store(); + this.store.setOwner(this); + } + return store; + } + + + } diff --git a/src/main/java/poomasi/domain/member/service/MemberService.java b/src/main/java/poomasi/domain/member/service/MemberService.java index c386ee3c..8d48c257 100644 --- a/src/main/java/poomasi/domain/member/service/MemberService.java +++ b/src/main/java/poomasi/domain/member/service/MemberService.java @@ -7,6 +7,10 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import poomasi.domain.member._profile.dto.request.AddressUpdateRequest; +import poomasi.domain.member._profile.entity.MemberProfile; +import poomasi.domain.member.dto.request.CustomerUpdateRequest; +import poomasi.domain.member.dto.request.FarmerUpdateRequest; import poomasi.domain.member.dto.response.MemberResponse; import poomasi.domain.member.dto.response.MemberSummaryResponse; import poomasi.domain.member.entity.LoginType; @@ -14,8 +18,11 @@ import poomasi.domain.member.repository.MemberRepository; import poomasi.domain.member.dto.request.SignupRequest; import poomasi.domain.member.dto.response.SignUpResponse; +import poomasi.domain.store.entity.Store; import poomasi.global.error.BusinessException; +import java.util.Optional; + import static poomasi.domain.member.entity.Role.ROLE_CUSTOMER; import static poomasi.domain.member.entity.Role.ROLE_FARMER; import static poomasi.global.error.BusinessError.*; @@ -63,15 +70,11 @@ public Page getAllMembersSummary(Pageable pageable) { } @Transactional - public void convertToFarmer(Member member, Boolean hasFarmerQualification) { + public void convertToFarmer(Member member) { if (member.isFarmer()) { throw new BusinessException(MEMBER_ALREADY_FARMER); } - if (!hasFarmerQualification) { - throw new BusinessException(INVALID_FARMER_QUALIFICATION); - } - member.setRole(ROLE_FARMER); memberRepository.save(member); } @@ -93,4 +96,57 @@ public Member findMemberById(Long memberId) { .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); } + @Transactional + public Member updateCustomer(Member member, CustomerUpdateRequest customerUpdateRequest) + { + if (!member.isCustomer()) { + throw new BusinessException(INVALID_ROLE); + } + + updateCommonAttributes(member, customerUpdateRequest.name(),customerUpdateRequest.email(), customerUpdateRequest.password(), customerUpdateRequest.phoneNumber()); + + return memberRepository.save(member); + } + + @Transactional + public Member updateFarmer(Member member, FarmerUpdateRequest farmerUpdateRequest) + { + if (!member.isFarmer()) { + throw new BusinessException(INVALID_ROLE); + } + + updateCommonAttributes(member, farmerUpdateRequest.name(), farmerUpdateRequest.email(), farmerUpdateRequest.password(), farmerUpdateRequest.phoneNumber()); + + Store store = member.getOrCreateStore(); + + if (farmerUpdateRequest.storeName() != null) { + store.setName(farmerUpdateRequest.storeName()); + } + if (farmerUpdateRequest.storeAddress() != null) { + store.setAddress(farmerUpdateRequest.storeAddress()); + } + + return memberRepository.save(member); + } + + private void updateCommonAttributes(Member member, String name, String email, String password, String phoneNumber) { + if (name != null) member.setName(name); + if (email != null) member.setEmail(email); + if (password != null) member.setPassword(passwordEncoder.encode(password)); + + MemberProfile profile = member.getOrCreateProfile(); + if (phoneNumber != null) { + profile.setPhoneNumber(phoneNumber); + } + } + + @Transactional + public void updateAddress(Member member, AddressUpdateRequest request) { + MemberProfile profile = member.getOrCreateProfile(); + + profile.setAddress(request.defaultAddress(), request.addressDetail(), request.coordinateX(), request.coordinateY()); + + memberRepository.save(member); + } + } diff --git a/src/main/java/poomasi/domain/order/_payment/entity/PaymentState.java b/src/main/java/poomasi/domain/order/_payment/entity/PaymentState.java deleted file mode 100644 index 77d04d38..00000000 --- a/src/main/java/poomasi/domain/order/_payment/entity/PaymentState.java +++ /dev/null @@ -1,31 +0,0 @@ -package poomasi.domain.order._payment.entity; - -public enum PaymentState { - PENDING, // 결제 대기 중 - COMPLETED, // 결제 완료 - FAILED, // 결제 실패 - CANCELLED, // 결제 취소됨 - REFUNDED, // 환불 완료 - DECLINED; // 결제 거부됨 - - @Override - public String toString() { - // 사용자 친화적인 문자열로 반환할 수 있도록 오버라이딩 - switch (this) { - case PENDING: - return "Payment Pending"; - case COMPLETED: - return "Payment Completed"; - case FAILED: - return "Payment Failed"; - case CANCELLED: - return "Payment Cancelled"; - case REFUNDED: - return "Payment Refunded"; - case DECLINED: - return "Payment Declined"; - default: - return super.toString(); - } - } -} diff --git a/src/main/java/poomasi/domain/order/_payment/service/PaymentService.java b/src/main/java/poomasi/domain/order/_payment/service/PaymentService.java deleted file mode 100644 index e5d33028..00000000 --- a/src/main/java/poomasi/domain/order/_payment/service/PaymentService.java +++ /dev/null @@ -1,149 +0,0 @@ -package poomasi.domain.order._payment.service; - -import com.siot.IamportRestClient.IamportClient; -import com.siot.IamportRestClient.exception.IamportResponseException; -import com.siot.IamportRestClient.request.CancelData; -import com.siot.IamportRestClient.request.PrepareData; -import com.siot.IamportRestClient.response.AccessToken; -import com.siot.IamportRestClient.response.IamportResponse; -import com.siot.IamportRestClient.response.Prepare; -import jdk.jfr.Description; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import poomasi.domain.auth.security.userdetail.UserDetailsImpl; -import poomasi.domain.member.entity.Member; -import poomasi.domain.order._payment.dto.request.PaymentPreRegisterRequest; -import poomasi.domain.order._payment.dto.request.PaymentWebHookRequest; -import poomasi.domain.order._payment.dto.response.PaymentPreRegisterResponse; -import poomasi.domain.order._payment.dto.response.PaymentResponse; -import poomasi.domain.order._payment.entity.Payment; -import poomasi.domain.order._payment.repository.PaymentRepository; -import poomasi.domain.order.entity.Order; -import poomasi.domain.order.repository.OrderRepository; -import poomasi.domain.product._cart.service.CartService; -import poomasi.global.error.BusinessError; -import poomasi.global.error.BusinessException; - -import java.io.IOException; -import java.math.BigDecimal; -import java.util.List; - -import static poomasi.domain.order.entity.OrderStatus.AWAITING_SELLER_CONFIRMATION; -import static poomasi.domain.order.entity.OrderStatus.PENDING; -import static poomasi.global.error.BusinessError.*; - -@Service -@RequiredArgsConstructor -@Slf4j -public class PaymentService { - - @Autowired - private final PaymentRepository paymentRepository; - private final IamportClient iamportClient; - private final OrderRepository orderRepository; - private final CartService cartService; - - @Description("포트원 api 호출을 위한 accessToken 발급 메서드") - private String getPortOneAccessToken() throws IOException, IamportResponseException { - IamportResponse authResponse = iamportClient.getAuth(); - String accessToken = authResponse.getResponse().getToken(); - return accessToken; - } - - @Description("사전 결제 등록") - public PaymentPreRegisterResponse portonePrePaymentRegister(PaymentPreRegisterRequest paymentPreRegisterRequest) throws IOException, IamportResponseException { - PrepareData prepareData = new PrepareData(paymentPreRegisterRequest.merchantUid(), - paymentPreRegisterRequest.amount() - ); - iamportClient.postPrepare(prepareData); - return PaymentPreRegisterResponse.from( - paymentPreRegisterRequest.merchantUid() - ); - } - - @Transactional - @Description("프론트에서 받아온 결과를 validate하는 메서드") - public void portoneVerifyPostPayment(PaymentWebHookRequest paymentWebHookRequest) throws IOException, IamportResponseException { - String impUid = paymentWebHookRequest.imp_uid(); - String merchantUid = paymentWebHookRequest.merchant_uid(); - IamportResponse iamportResponse = iamportClient.paymentByImpUid(impUid); - BigDecimal amount = iamportResponse.getResponse() - .getAmount(); - - Order order = orderRepository.findByMerchantUid(merchantUid) - .orElseThrow(() -> new BusinessException(ORDER_NOT_FOUND)); - - if(order.getOrderStatus()!=PENDING){ //이미 처리한 주문이라면 - throw new BusinessException(ORDER_ALREADY_PROCESSED); - } - - if(validatePaymentConsistency(order.getTotalAmount(), amount)){ //결제 금액이 맞지 않다면 -> 주문 취소 api 호출 - cancelPayment(iamportResponse); - throw new BusinessException(PAYMENT_AMOUNT_MISMATCH); - } - order.setOrderStatus(AWAITING_SELLER_CONFIRMATION); // 상태 변경 - cartService.removeSelected(); //장바구니 삭제 - } - - private boolean validatePaymentConsistency(BigDecimal prepaymentAmount, BigDecimal postPaymentAmount){ - if (prepaymentAmount.compareTo(postPaymentAmount) != 0) { - return false; - } - return true; - } - - @Description("payment 상세 내역 조회를 위한 단건 api 호출") - public void getPaymentDetails(String merchantUid, Long orderId) throws IOException, IamportResponseException { - - } - - @Description("결제 취소 api") - public void cancelPayment(IamportResponse response) throws IOException, IamportResponseException{ - //true면 Uid, false면 merchantUid로 판단 - CancelData cancelData = new CancelData(response.getResponse().getMerchantUid(), false); - iamportClient.cancelPaymentByImpUid(cancelData); - } - - @Description("결제 환불 api") - public void processRefund() throws IOException, IamportResponseException{ - - } - - - @Description("결제 부분 환불 api 호출") - public void partialRefund() throws IOException, IamportResponseException { - - } - - public PaymentResponse getPayment(Long paymentId) { - Payment payment = paymentRepository.findById(paymentId) - .orElseThrow(() -> new BusinessException(PAYMENT_NOT_FOUND)); - return PaymentResponse.fromEntity(payment); - } - - @Description("orderID로 결제 방법 찾는 메서드") - public PaymentResponse getPaymentByOrderId(Long orderId) { - Member member = getMember(); - Payment payment = paymentRepository.findById(orderId) - .orElseThrow(() -> new BusinessException(PAYMENT_NOT_FOUND)); - return PaymentResponse.fromEntity(payment); - } - - - private Member getMember() { - Authentication authentication = SecurityContextHolder - .getContext().getAuthentication(); - Object impl = authentication.getPrincipal(); - Member member = ((UserDetailsImpl) impl).getMember(); - return member; - } - -} - - diff --git a/src/main/java/poomasi/domain/order/_payment/util/PaymentUtil.java b/src/main/java/poomasi/domain/order/_payment/util/PaymentUtil.java index ce1d9212..c5c571d8 100644 --- a/src/main/java/poomasi/domain/order/_payment/util/PaymentUtil.java +++ b/src/main/java/poomasi/domain/order/_payment/util/PaymentUtil.java @@ -35,8 +35,8 @@ public BigDecimal getPaymentAmount(String impUid) throws IOException, IamportRes } @Description("단건 결제 조회 API") - public IamportResponse getSingleTransaction(String impUid) throws IOException, IamportResponseException { - IamportResponse iamportResponse = iamportClient.paymentByImpUid(impUid); + public IamportResponse getSingleTransaction(String impUid) throws IOException, IamportResponseException { + IamportResponse iamportResponse = iamportClient.paymentByImpUid(impUid); return iamportResponse; } @@ -73,7 +73,7 @@ public void sendPrepareData(String merchantUid, BigDecimal amount) throws IOExce @Description("단건 조회 후, 결제 되어야 할 금액과 결제 된 금액이 같은지 확인하는 메서드") public boolean validatePaymentAmount(String impUid, BigDecimal amountToBePaid) throws IOException, IamportResponseException{ - IamportResponse iamportResponse = getSingleTransaction(impUid); //내가 보냄 + IamportResponse iamportResponse = getSingleTransaction(impUid); //내가 보냄 BigDecimal amount = iamportResponse.getResponse().getAmount(); if(amountToBePaid.compareTo(amount)!=0){ return false; diff --git a/src/main/java/poomasi/domain/order/_refund/entity/Refund.java b/src/main/java/poomasi/domain/order/_refund/entity/Refund.java deleted file mode 100644 index 8b51294d..00000000 --- a/src/main/java/poomasi/domain/order/_refund/entity/Refund.java +++ /dev/null @@ -1,20 +0,0 @@ -package poomasi.domain.order._refund.entity; - - -import jakarta.persistence.*; -import poomasi.domain.order.entity.OrderProductDetails; - -@Entity -@Table(name="refund") -public class Refund { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - -/* @OneToOne(fetch = FetchType.LAZY) - private OrderProductDetails orderProductDetails;*/ - - private String refundReason; - -} diff --git a/src/main/java/poomasi/domain/order/_refund/entity/RefundStatus.java b/src/main/java/poomasi/domain/order/_refund/entity/RefundStatus.java deleted file mode 100644 index 69a08f7c..00000000 --- a/src/main/java/poomasi/domain/order/_refund/entity/RefundStatus.java +++ /dev/null @@ -1,29 +0,0 @@ -package poomasi.domain.order._refund.entity; - -public enum RefundStatus { - REQUESTED("환불 요청됨"), - PROCESSING("환불 처리 중"), - COMPLETED("환불 완료됨"), - REJECTED("환불 거절됨"); - - private final String description; - - RefundStatus(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } -} - -/* -* REQUESTED, // 반품 요청됨 - APPROVED, // 반품 승인됨 - REJECTED, // 반품 거부됨 - RETURNED, // 상품이 반품됨 - REFUNDED, // 환불 완료됨 - CANCELLED, // 반품 요청이 취소됨 - IN_TRANSIT // 반품이 배송 중 - ; -* */ diff --git a/src/main/java/poomasi/domain/order/entity/AbstractOrder.java b/src/main/java/poomasi/domain/order/entity/AbstractOrder.java deleted file mode 100644 index 82a7c4ff..00000000 --- a/src/main/java/poomasi/domain/order/entity/AbstractOrder.java +++ /dev/null @@ -1,33 +0,0 @@ -package poomasi.domain.order.entity; - -import jakarta.persistence.*; -import jdk.jfr.Description; -import jdk.jfr.Timestamp; -import lombok.Getter; -import org.hibernate.annotations.UpdateTimestamp; -import poomasi.domain.member.entity.Member; - -import java.time.LocalDateTime; -import java.util.Date; - -@MappedSuperclass -@Getter -public abstract class AbstractOrder { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @JoinColumn(name = "member_id") - @ManyToOne(fetch = FetchType.LAZY) - private Member member; - - @Column(name = "merchant_uid") - @Description("상품당 결제 id(아임포트 id)") - private String merchantUid = "p" + new Date().getTime(); - - @Column(name = "created_at") - @Timestamp - private LocalDateTime createdAt = LocalDateTime.now(); - -} diff --git a/src/main/java/poomasi/domain/order/entity/Order.java b/src/main/java/poomasi/domain/order/entity/Order.java deleted file mode 100644 index a830bed2..00000000 --- a/src/main/java/poomasi/domain/order/entity/Order.java +++ /dev/null @@ -1,49 +0,0 @@ -package poomasi.domain.order.entity; - - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.math.BigDecimal; -import java.util.List; - -import static poomasi.domain.order.entity.OrderStatus.PENDING; - -@Entity -@Table(name = "orders") -@Getter -@NoArgsConstructor -public class Order extends AbstractOrder{ - - @Column(name = "order_product_details_id") - @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) - private List orderProductDetails; - - @OneToOne - @JoinColumn(name = "order_details_id") // 여기서 JoinColumn 사용 - private OrderDetails orderDetails; - - @Column(name = "total_amount") - private BigDecimal totalAmount; - - @Enumerated(EnumType.STRING) - private OrderStatus orderStatus = OrderStatus.PENDING; - - public Order(OrderDetails orderDetails) { - this.orderDetails = orderDetails; - } - - public void addOrderDetail(OrderProductDetails orderProductDetails) { - this.orderProductDetails.add(orderProductDetails); - } - public void setOrderStatus(OrderStatus orderStatus) { - this.orderStatus = orderStatus; - } - - public void setTotalAmount(BigDecimal totalAmount) { - this.totalAmount = totalAmount; - } - - -} diff --git a/src/main/java/poomasi/domain/order/entity/OrderDetails.java b/src/main/java/poomasi/domain/order/entity/OrderDetails.java deleted file mode 100644 index 5bb7ed09..00000000 --- a/src/main/java/poomasi/domain/order/entity/OrderDetails.java +++ /dev/null @@ -1,38 +0,0 @@ -package poomasi.domain.order.entity; - -import jakarta.persistence.*; -import jdk.jfr.Description; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name="order_details") -@Getter -@NoArgsConstructor -public class OrderDetails { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @OneToOne(mappedBy = "orderDetails") - private Order order; - - @Column(name = "address") - private String address; - - @Column(name = "address_detail") - private String addressDetail; - - @Description("배송 요청 사항") - @Column(name = "delivery_request", length = 255) - private String deliveryRequest; - - public OrderDetails(String address, String addressDetail, String deliveryRequest) { - this.address = address; - this.addressDetail = addressDetail; - this.deliveryRequest = deliveryRequest; - } - - -} diff --git a/src/main/java/poomasi/domain/order/entity/OrderProductDetails.java b/src/main/java/poomasi/domain/order/entity/OrderProductDetails.java deleted file mode 100644 index 5be2e668..00000000 --- a/src/main/java/poomasi/domain/order/entity/OrderProductDetails.java +++ /dev/null @@ -1,71 +0,0 @@ -package poomasi.domain.order.entity; - - -import jakarta.persistence.*; -import jdk.jfr.Description; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import poomasi.domain.order._refund.entity.Refund; -import poomasi.domain.product.entity.Product; - -import java.io.Serializable; -import java.math.BigDecimal; - -@Entity -@Table(name = "order_product_details") -@Getter -@NoArgsConstructor -public class OrderProductDetails implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "order_product_details_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "product_id") - private Product product; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "order_id") - private Order order; - - /* @OneToOne(fetch = FetchType.LAZY) - private Refund refund;*/ - - @Column(name = "product_description", nullable = true) - private String productDescription; - - @Column(name = "product_name", length = 255) - private String productName; - - @Description("구매 당시 1개당 가격") - private BigDecimal price; - - @Column(name="count") - private Integer count; - - @Description("송장 번호") - @Column(name = "invoice_number") - private String invoiceNumber; - - //private String sellerName; - //private OneToMany Review; - //refund.. - - @Builder - public OrderProductDetails(Product product, Order order, String productDescription, String productName, BigDecimal price, Integer count) { - this.product = product; - this.order = order; - this.productDescription = productDescription; - this.productName = productName; - this.price = price; - this.count = count; - } - - public void setInvoiceNumber(String invoiceNumber) { - this.invoiceNumber = invoiceNumber; - } -} - diff --git a/src/main/java/poomasi/domain/order/entity/OrderStatus.java b/src/main/java/poomasi/domain/order/entity/OrderStatus.java deleted file mode 100644 index 6a3b07a8..00000000 --- a/src/main/java/poomasi/domain/order/entity/OrderStatus.java +++ /dev/null @@ -1,11 +0,0 @@ -package poomasi.domain.order.entity; - -public enum OrderStatus { - PENDING, // 결제 대기 중 - AWAITING_SELLER_CONFIRMATION, // 판매자 확인 대기 중 - READY_FOR_SHIPMENT, // 배송 대기 중 - IN_TRANSIT, // 배송 중 - DELIVERED, // 배송 완료 - ORDER_COMPLETE // 주문 완료 - ; -} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/order/repository/OrderProductDetailsRepository.java b/src/main/java/poomasi/domain/order/repository/OrderProductDetailsRepository.java deleted file mode 100644 index 61432033..00000000 --- a/src/main/java/poomasi/domain/order/repository/OrderProductDetailsRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package poomasi.domain.order.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import poomasi.domain.order.entity.OrderProductDetails; - -import java.util.List; - -public interface OrderProductDetailsRepository extends JpaRepository { - List findByOrderId(Long orderId); -} diff --git a/src/main/java/poomasi/domain/order/repository/OrderRepository.java b/src/main/java/poomasi/domain/order/repository/OrderRepository.java deleted file mode 100644 index f2476ad0..00000000 --- a/src/main/java/poomasi/domain/order/repository/OrderRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package poomasi.domain.order.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import poomasi.domain.order.entity.Order; -import poomasi.domain.order.entity.OrderProductDetails; - -import java.util.List; -import java.util.Optional; - -public interface OrderRepository extends JpaRepository { - List findByMemberId(Long memberId); - //List findById(Long id); - Optional findByMerchantUid(String merchantUid); -} diff --git a/src/main/java/poomasi/domain/product/entity/Product.java b/src/main/java/poomasi/domain/product/entity/Product.java index cd80bfbc..fcb8d69e 100644 --- a/src/main/java/poomasi/domain/product/entity/Product.java +++ b/src/main/java/poomasi/domain/product/entity/Product.java @@ -8,6 +8,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import org.hibernate.annotations.Comment; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -38,6 +39,7 @@ public class Product { @Comment("상품 설명") private String description; + @Setter @Comment("이미지 URL") private String imageUrl; @@ -77,6 +79,13 @@ public class Product { @JoinColumn(name = "order_product_details_id") private List orderProductDetails; +// @PreRemove +// public void preRemove() { +// // Product가 삭제되기 전에 연관된 이미지를 삭제 +// for (Image image : images) { +// image.setDeletedAt(LocalDateTime.now()); +// } +// } @Builder public Product(Long productId, diff --git a/src/main/java/poomasi/domain/product/service/ProductService.java b/src/main/java/poomasi/domain/product/service/ProductService.java index 79b73dca..f2b75ce1 100644 --- a/src/main/java/poomasi/domain/product/service/ProductService.java +++ b/src/main/java/poomasi/domain/product/service/ProductService.java @@ -39,4 +39,8 @@ public Product findProductById(Long productId) { return productRepository.findByIdAndDeletedAtIsNull(productId) .orElseThrow(() -> new BusinessException(BusinessError.PRODUCT_NOT_FOUND)); } + + public void saveExistedProduct(Product product) { + productRepository.save(product); + } } diff --git a/src/main/java/poomasi/domain/store/entity/Store.java b/src/main/java/poomasi/domain/store/entity/Store.java index c4be3f96..84d24c95 100644 --- a/src/main/java/poomasi/domain/store/entity/Store.java +++ b/src/main/java/poomasi/domain/store/entity/Store.java @@ -13,6 +13,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import org.hibernate.annotations.Comment; import poomasi.domain.member.entity.Member; import poomasi.domain.store.dto.StoreRegisterRequest; @@ -26,10 +27,15 @@ public class Store { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Setter private String name; + + @Setter private String address; private String phone; + @Setter @OneToOne(fetch = FetchType.LAZY) private Member owner; diff --git a/src/main/java/poomasi/global/config/s3/S3PresignedUrlController.java b/src/main/java/poomasi/global/config/s3/S3PresignedUrlController.java index 44f5e52b..46b49f7b 100644 --- a/src/main/java/poomasi/global/config/s3/S3PresignedUrlController.java +++ b/src/main/java/poomasi/global/config/s3/S3PresignedUrlController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.*; import poomasi.global.config.aws.AwsProperties; import poomasi.global.config.s3.dto.request.PresignedUrlPutRequest; +import poomasi.global.config.s3.dto.response.PresignedPutUrlResponse; @RestController @RequiredArgsConstructor @@ -24,7 +25,10 @@ public ResponseEntity presignedUrlGet(@RequestParam String keyname) { @PostMapping("/presigned-url-put") @Secured({"ROLE_CUSTOMER", "ROLE_FARMER", "ROLE_ADMIN"}) public ResponseEntity presignedUrlPut(@RequestBody PresignedUrlPutRequest request) { - String presignedPutUrl = s3PresignedUrlService.createPresignedPutUrl(awsProperties.getS3().getBucket(), request.keyPrefix(), request.metadata()); + PresignedPutUrlResponse presignedPutUrl = s3PresignedUrlService.createPresignedPutUrl( + awsProperties.getS3().getBucket(), + awsProperties.getS3().getRegion(), + request.keyPrefix(), request.metadata()); return ResponseEntity.ok(presignedPutUrl); } } diff --git a/src/main/java/poomasi/global/config/s3/S3PresignedUrlService.java b/src/main/java/poomasi/global/config/s3/S3PresignedUrlService.java index 241375f3..b8541f7d 100644 --- a/src/main/java/poomasi/global/config/s3/S3PresignedUrlService.java +++ b/src/main/java/poomasi/global/config/s3/S3PresignedUrlService.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import poomasi.global.config.s3.dto.response.PresignedPutUrlResponse; import poomasi.global.util.EncryptionUtil; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; @@ -46,14 +47,13 @@ public String createPresignedGetUrl(String bucketName, String keyName) { } - public String createPresignedPutUrl(String bucketName, String keyPrefix, Map metadata) { + public PresignedPutUrlResponse createPresignedPutUrl(String bucketName, String region, String keyPrefix, Map metadata) { LocalDateTime now = LocalDateTime.now(); String date = now.format(DATE_FORMATTER); String encodedTime = encryptionUtil.encodeTime(now).substring(0, 10); // jpg 말고 다른 형식 파일 들어오는 경우에 대해서도 따로 처리 필요 // 사진 갯수 5개로 제한하기 - // 극악의 확률로 url이 겹치면?? -> 그럴일 거의 없긴할텐데 생기면 s3 원래 파일 지워짐 String uniqueIdentifier = UUID.randomUUID().toString(); String keyName = String.format("%s/%s/%s_%s.jpg", keyPrefix, date, uniqueIdentifier, encodedTime); @@ -74,7 +74,9 @@ public String createPresignedPutUrl(String bucketName, String keyPrefix, Map mockResponse = new IamportResponse<>(); - given(iamportClient.postPrepare(any(PrepareData.class))).willReturn(mockResponse); +// @Test +// public void portonePrePaymentRegister_Test() throws IamportResponseException, IOException { +// String merchantUid = "poomasi_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); +// BigDecimal amount = new BigDecimal("100"); +// PrepareData prepareData = new PrepareData(merchantUid, amount); +// +// // `iamportClient.postPrepare` 모킹 설정 +// IamportResponse mockResponse = new IamportResponse<>(); +// given(iamportClient.postPrepare(any(PrepareData.class))).willReturn(mockResponse); +// +// // 테스트 실행 +// IamportResponse prepareIamportResponse = iamportClient.postPrepare(prepareData); +// System.out.println("Response Code: " + prepareIamportResponse.getCode()); +// System.out.println("Response Message: " + prepareIamportResponse.getMessage()); +// } - // 테스트 실행 - IamportResponse prepareIamportResponse = iamportClient.postPrepare(prepareData); - System.out.println("Response Code: " + prepareIamportResponse.getCode()); - System.out.println("Response Message: " + prepareIamportResponse.getMessage()); - } } diff --git a/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java b/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java index d98d9ea6..aa41f765 100644 --- a/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java +++ b/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java @@ -24,53 +24,53 @@ public class S3PresignedUrlServiceTest { @Autowired private AwsProperties awsProperties; - @BeforeEach - public void setUp() { - String accessKey = awsProperties.getAccess(); - String secretKey = awsProperties.getSecret(); - String region = awsProperties.getS3().getRegion(); - - // 자격 증명 설정 - AwsBasicCredentials awsCreds = AwsBasicCredentials.create( - accessKey, - secretKey - ); - - // S3Presigner 인스턴스 생성 - S3Presigner presigner = S3Presigner.builder() - .credentialsProvider(StaticCredentialsProvider.create(awsCreds)) - .region(Region.of(region)) - .build(); - - // S3PresignedUrlService 초기화 - s3PresignedUrlService = new S3PresignedUrlService(presigner, new EncryptionUtil()); - } - - @Test - public void testCreatePresignedGetUrl() { - String objectKey = "object_key"; - String bucketName = awsProperties.getS3().getBucket(); - - String presignedUrl = s3PresignedUrlService.createPresignedGetUrl(bucketName, objectKey); - - assertNotNull(presignedUrl); - System.out.println("Presigned GET URL: " + presignedUrl); - } - - @Test - public void testCreatePresignedPutUrl() { - String keyPrefix = "uploads"; - String bucketName = awsProperties.getS3().getBucket(); - - // 메타데이터 생성 - Map metadata = new HashMap<>(); - metadata.put("Content-Type", "image/jpg"); - metadata.put("x-amz-meta-title", "Test Image"); - - // presigned PUT URL 생성 - String presignedUrl = s3PresignedUrlService.createPresignedPutUrl(bucketName, keyPrefix, metadata); - - assertNotNull(presignedUrl); - System.out.println("Presigned PUT URL: " + presignedUrl); - } +// @BeforeEach +// public void setUp() { +// String accessKey = awsProperties.getAccess(); +// String secretKey = awsProperties.getSecret(); +// String region = awsProperties.getS3().getRegion(); +// +// // 자격 증명 설정 +// AwsBasicCredentials awsCreds = AwsBasicCredentials.create( +// accessKey, +// secretKey +// ); +// +// // S3Presigner 인스턴스 생성 +// S3Presigner presigner = S3Presigner.builder() +// .credentialsProvider(StaticCredentialsProvider.create(awsCreds)) +// .region(Region.of(region)) +// .build(); +// +// // S3PresignedUrlService 초기화 +// s3PresignedUrlService = new S3PresignedUrlService(presigner, new EncryptionUtil()); +// } +// +// @Test +// public void testCreatePresignedGetUrl() { +// String objectKey = "object_key"; +// String bucketName = awsProperties.getS3().getBucket(); +// +// String presignedUrl = s3PresignedUrlService.createPresignedGetUrl(bucketName, objectKey); +// +// assertNotNull(presignedUrl); +// System.out.println("Presigned GET URL: " + presignedUrl); +// } +// +// @Test +// public void testCreatePresignedPutUrl() { +// String keyPrefix = "uploads"; +// String bucketName = awsProperties.getS3().getBucket(); +// +// // 메타데이터 생성 +// Map metadata = new HashMap<>(); +// metadata.put("Content-Type", "image/jpg"); +// metadata.put("x-amz-meta-title", "Test Image"); +// +// // presigned PUT URL 생성 +// String presignedUrl = s3PresignedUrlService.createPresignedPutUrl(bucketName, keyPrefix, metadata); +// +// assertNotNull(presignedUrl); +// System.out.println("Presigned PUT URL: " + presignedUrl); +// } }