diff --git a/sunsu-wedding/gallery/10a0d398-cdfc-4f8e-b4a9-a7af271a894c_Crossfit_Training_GYM_16^9.jpg b/sunsu-wedding/gallery/10a0d398-cdfc-4f8e-b4a9-a7af271a894c_Crossfit_Training_GYM_16^9.jpg deleted file mode 100644 index d6cd7750..00000000 Binary files a/sunsu-wedding/gallery/10a0d398-cdfc-4f8e-b4a9-a7af271a894c_Crossfit_Training_GYM_16^9.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/1ba0b185-f0c2-4f7c-a69e-313a454639cd_Crossfit_Training_GYM_margin.jpg b/sunsu-wedding/gallery/1ba0b185-f0c2-4f7c-a69e-313a454639cd_Crossfit_Training_GYM_margin.jpg deleted file mode 100644 index 5a32eb47..00000000 Binary files a/sunsu-wedding/gallery/1ba0b185-f0c2-4f7c-a69e-313a454639cd_Crossfit_Training_GYM_margin.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/1fb60d99-d2c3-4f35-8a3f-17334779370d_Crossfit_Training_GYM_16^9.jpg b/sunsu-wedding/gallery/1fb60d99-d2c3-4f35-8a3f-17334779370d_Crossfit_Training_GYM_16^9.jpg deleted file mode 100644 index d6cd7750..00000000 Binary files a/sunsu-wedding/gallery/1fb60d99-d2c3-4f35-8a3f-17334779370d_Crossfit_Training_GYM_16^9.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/5b37fbe3-3083-4cbd-94f6-e0a595a4cf4d_Crossfit_Training_GYM.jpg b/sunsu-wedding/gallery/5b37fbe3-3083-4cbd-94f6-e0a595a4cf4d_Crossfit_Training_GYM.jpg deleted file mode 100644 index 096cf7d6..00000000 Binary files a/sunsu-wedding/gallery/5b37fbe3-3083-4cbd-94f6-e0a595a4cf4d_Crossfit_Training_GYM.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/77acbbe7-1dff-4613-b98f-9ef460c1a5aa_Crossfit_Training_GYM_margin.jpg b/sunsu-wedding/gallery/77acbbe7-1dff-4613-b98f-9ef460c1a5aa_Crossfit_Training_GYM_margin.jpg deleted file mode 100644 index 5a32eb47..00000000 Binary files a/sunsu-wedding/gallery/77acbbe7-1dff-4613-b98f-9ef460c1a5aa_Crossfit_Training_GYM_margin.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/993db779-7c7d-4584-a0a4-f79db1ccfe91_Crossfit_Training_GYM.jpg b/sunsu-wedding/gallery/993db779-7c7d-4584-a0a4-f79db1ccfe91_Crossfit_Training_GYM.jpg deleted file mode 100644 index 096cf7d6..00000000 Binary files a/sunsu-wedding/gallery/993db779-7c7d-4584-a0a4-f79db1ccfe91_Crossfit_Training_GYM.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/a1573eaf-699f-48db-baaa-a60601021372_Crossfit_Training_GYM.jpg b/sunsu-wedding/gallery/a1573eaf-699f-48db-baaa-a60601021372_Crossfit_Training_GYM.jpg deleted file mode 100644 index 096cf7d6..00000000 Binary files a/sunsu-wedding/gallery/a1573eaf-699f-48db-baaa-a60601021372_Crossfit_Training_GYM.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/d5f906c2-2f67-4de9-ad08-d0b14df87084_Crossfit_Training_GYM_margin.jpg b/sunsu-wedding/gallery/d5f906c2-2f67-4de9-ad08-d0b14df87084_Crossfit_Training_GYM_margin.jpg deleted file mode 100644 index 5a32eb47..00000000 Binary files a/sunsu-wedding/gallery/d5f906c2-2f67-4de9-ad08-d0b14df87084_Crossfit_Training_GYM_margin.jpg and /dev/null differ diff --git a/sunsu-wedding/gallery/fcc7c177-20cb-47a9-b999-006374754723_Crossfit_Training_GYM_16^9.jpg b/sunsu-wedding/gallery/fcc7c177-20cb-47a9-b999-006374754723_Crossfit_Training_GYM_16^9.jpg deleted file mode 100644 index d6cd7750..00000000 Binary files a/sunsu-wedding/gallery/fcc7c177-20cb-47a9-b999-006374754723_Crossfit_Training_GYM_16^9.jpg and /dev/null differ diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/BaseException.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/BaseException.java index 600b221b..08564098 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/BaseException.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/BaseException.java @@ -17,11 +17,11 @@ public enum BaseException { PORTFOLIO_NOT_FOUND("포트폴리오를 찾을 수 없습니다.", 404), PORTFOLIO_IMAGE_NOT_FOUND("포트폴리오 이미지를 불러올 수 없습니다.", 404), PERMISSION_DENIED_METHOD_ACCESS("사용할 수 없는 기능입니다.", 403), + DATABASE_ERROR("데이터베이스 에러입니다", 500), QUOTATIONS_NOT_ALL_CONFIRMED("확정되지 않은 견적서가 있습니다.",400), NO_QUOTATION_TO_CONFIRM("확정할 견적서가 없습니다",400), MATCHING_NOT_FOUND("매칭 내역을 찾을 수 없습니다.", 400); - @Getter private final String message; diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/GlobalExceptionHandler.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/GlobalExceptionHandler.java index c9e6d41f..7903ae11 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/GlobalExceptionHandler.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ import com.kakao.sunsuwedding._core.errors.exception.*; import com.kakao.sunsuwedding._core.utils.ApiUtils; +import org.springframework.dao.DataAccessException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.ObjectError; @@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import java.sql.SQLException; import java.util.List; @ControllerAdvice @@ -21,6 +23,14 @@ public ResponseEntity validationException(MethodArgumentNotValidException e) return new ResponseEntity<>(ApiUtils.error(errors.get(0).getDefaultMessage(), HttpStatus.BAD_REQUEST), HttpStatus.BAD_REQUEST); } + // database에 잘못된 값이 들어온 경우 + // 예: unique 값인데 같은 게 또 들어온 경우 + @ExceptionHandler({SQLException.class, DataAccessException.class}) + public ResponseEntity databaseException(){ + BaseException e = BaseException.DATABASE_ERROR; + return new ResponseEntity<>(ApiUtils.error(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR), HttpStatus.INTERNAL_SERVER_ERROR); + } + @ExceptionHandler(Exception400.class) public ResponseEntity badRequest(Exception400 e){ return new ResponseEntity<>(e.body(), e.status()); diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/GlobalValidationHandler.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/GlobalValidationHandler.java deleted file mode 100644 index 649e9c4b..00000000 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/errors/GlobalValidationHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.kakao.sunsuwedding._core.errors; - -import com.kakao.sunsuwedding._core.errors.exception.Exception400; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.aspectj.lang.annotation.Pointcut; -import org.springframework.stereotype.Component; -import org.springframework.validation.Errors; - -@Aspect -@Component -public class GlobalValidationHandler { - @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)") - public void postMapping() { - } - - @Before("postMapping()") - public void validationAdvice(JoinPoint jp) { - Object[] args = jp.getArgs(); - for (Object arg : args) { - if (arg instanceof Errors) { - Errors errors = (Errors) arg; - - if (errors.hasErrors()) { - throw new Exception400( - errors.getFieldErrors().get(0).getDefaultMessage() - ); - } - } - } - } -} diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetails.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetails.java index 505bf034..45311014 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetails.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetails.java @@ -1,8 +1,6 @@ package com.kakao.sunsuwedding._core.security; -import com.kakao.sunsuwedding.user.constant.Role; -import com.kakao.sunsuwedding.user.couple.Couple; -import com.kakao.sunsuwedding.user.planner.Planner; +import com.kakao.sunsuwedding.user.base_user.User; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.data.util.Pair; @@ -17,29 +15,25 @@ @Getter public class CustomUserDetails implements UserDetails { - private final Planner planner; - - private final Couple couple; + private final User user; // security 에 사용하기 위한 권한 설정(플래너 -> planner, 커플 -> couple) @Override public Collection getAuthorities() { - return Collections.singleton(new SimpleGrantedAuthority(getInfo().getFirst().getRoleName())); + return Collections.singleton(new SimpleGrantedAuthority(user.getDtype())); } - - // role(planner or couple), userId - public Pair getInfo(){ - return (couple == null) ? Pair.of(Role.PLANNER, planner.getId()) : Pair.of(Role.COUPLE, couple.getId()); + public Pair getInfo() { + return Pair.of(user.getDtype(), user.getId()); } @Override public String getPassword() { - return (couple == null) ? planner.getPassword() : couple.getPassword(); + return user.getPassword(); } @Override public String getUsername() { - return (couple == null) ? planner.getEmail() : couple.getEmail(); + return user.getEmail(); } @Override diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetailsService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetailsService.java index 1dbfd25f..7cec8880 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetailsService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/CustomUserDetailsService.java @@ -1,38 +1,26 @@ package com.kakao.sunsuwedding._core.security; import com.kakao.sunsuwedding._core.errors.BaseException; -import com.kakao.sunsuwedding._core.errors.exception.Exception400; -import com.kakao.sunsuwedding.user.couple.Couple; -import com.kakao.sunsuwedding.user.couple.CoupleJPARepository; -import com.kakao.sunsuwedding.user.planner.Planner; -import com.kakao.sunsuwedding.user.planner.PlannerJPARepository; +import com.kakao.sunsuwedding.user.base_user.User; +import com.kakao.sunsuwedding.user.base_user.UserJPARepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import java.util.Optional; - @RequiredArgsConstructor @Service public class CustomUserDetailsService implements UserDetailsService { - private final PlannerJPARepository plannerJPARepository; - private final CoupleJPARepository coupleJPARepository; + private final UserJPARepository userJPARepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - Optional plannerOP = plannerJPARepository.findByEmail(email); - Optional coupleOP = coupleJPARepository.findByEmail(email); - - Planner plannerPS = plannerOP.orElse(null); - Couple couplePS = coupleOP.orElse(null); - - // 잘못된 email (플래너도 아니고, 예비 부부도 아님) - if (plannerPS == null && couplePS == null) - throw new Exception400(BaseException.USER_NOT_FOUND.getMessage()); + User user = userJPARepository.findByEmailNative(email).orElseThrow( + () -> new UsernameNotFoundException(BaseException.USER_EMAIL_NOT_FOUND.getMessage()) + ); - return new CustomUserDetails(plannerPS, couplePS); + return new CustomUserDetails(user); } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JWTProvider.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JWTProvider.java index 90ddd3fa..93f65038 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JWTProvider.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JWTProvider.java @@ -6,9 +6,7 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.DecodedJWT; -import com.kakao.sunsuwedding.user.constant.Role; -import com.kakao.sunsuwedding.user.couple.Couple; -import com.kakao.sunsuwedding.user.planner.Planner; +import com.kakao.sunsuwedding.user.base_user.User; import org.springframework.stereotype.Component; import java.util.Date; @@ -21,21 +19,12 @@ public class JWTProvider { public static final String HEADER = "Authorization"; public static final String SECRET = "MySecretKey"; - public static String create(Couple couple) { + public static String create(User user) { String jwt = JWT.create() - .withSubject(couple.getEmail()) + .withSubject(user.getEmail()) .withExpiresAt(new Date(System.currentTimeMillis() + EXP)) - .withClaim("id", couple.getId()) - .withClaim("role", Role.COUPLE.getRoleName()) - .sign(Algorithm.HMAC512(SECRET)); - return TOKEN_PREFIX + jwt; - } - public static String create(Planner planner) { - String jwt = JWT.create() - .withSubject(planner.getEmail()) - .withExpiresAt(new Date(System.currentTimeMillis() + EXP)) - .withClaim("id", planner.getId()) - .withClaim("role", Role.PLANNER.getRoleName()) + .withClaim("id", user.getId()) + .withClaim("role", user.getDtype()) .sign(Algorithm.HMAC512(SECRET)); return TOKEN_PREFIX + jwt; } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtAuthenticationFilter.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtAuthenticationFilter.java index 18849120..35c56067 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtAuthenticationFilter.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtAuthenticationFilter.java @@ -5,6 +5,7 @@ import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.DecodedJWT; +import com.kakao.sunsuwedding.user.base_user.User; import com.kakao.sunsuwedding.user.constant.Role; import com.kakao.sunsuwedding.user.couple.Couple; import com.kakao.sunsuwedding.user.planner.Planner; @@ -36,22 +37,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse chain.doFilter(request, response); return; } - if (!jwt.startsWith("Bearer ")) { - throw new JWTDecodeException("토큰 형식이 잘못되었습니다."); - } try { DecodedJWT decodedJWT = JWTProvider.verify(jwt); - int id = decodedJWT.getClaim("id").asInt(); - String roleName = decodedJWT.getClaim("role").asString(); - System.out.println("role : "+ roleName); + Long userId = decodedJWT.getClaim("id").asLong(); + String roleName = decodedJWT.getClaim("role").asString(); Role role = Role.valueOfRole(roleName); - if (role == null) - throw new JWTDecodeException("role 잘못됨"); - - Planner planner = (role == Role.PLANNER) ? Planner.builder().id(id).build() : null; - Couple couple = (role == Role.COUPLE) ? Couple.builder().id(id).build() : null; - CustomUserDetails myUserDetails = new CustomUserDetails(planner, couple); + User user = (role == Role.PLANNER) ? Planner.builder().id(userId).build() : Couple.builder().id(userId).build(); + CustomUserDetails myUserDetails = new CustomUserDetails(user); Authentication authentication = new UsernamePasswordAuthenticationToken( diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtExceptionFilter.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtExceptionFilter.java index 1d69de7f..6c673976 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtExceptionFilter.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/JwtExceptionFilter.java @@ -8,6 +8,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -16,7 +17,12 @@ @Component public class JwtExceptionFilter extends OncePerRequestFilter { - private final ObjectMapper om = new ObjectMapper(); + private final ObjectMapper om; + + @Autowired + public JwtExceptionFilter(ObjectMapper om) { + this.om = om; + } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/SecurityConfig.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/SecurityConfig.java index d6ce910a..09211437 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/SecurityConfig.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/security/SecurityConfig.java @@ -3,6 +3,7 @@ import com.kakao.sunsuwedding._core.errors.exception.Exception401; import com.kakao.sunsuwedding._core.errors.exception.Exception403; import com.kakao.sunsuwedding._core.utils.FilterResponseUtils; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -17,21 +18,24 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - +@RequiredArgsConstructor @Configuration public class SecurityConfig { + private final JwtExceptionFilter jwtExceptionFilter; + private final FilterResponseUtils filterResponseUtils; + @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } - public static class CustomSecurityFilterManager extends AbstractHttpConfigurer { + public class CustomSecurityFilterManager extends AbstractHttpConfigurer { @Override public void configure(HttpSecurity builder) throws Exception { AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); builder.addFilter(new JwtAuthenticationFilter(authenticationManager)); - builder.addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class); + builder.addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class); super.configure(builder); } } @@ -62,14 +66,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 8. 인증 실패 처리 http.exceptionHandling((exceptionHandling) -> exceptionHandling.authenticationEntryPoint((request, response, authException) -> { - FilterResponseUtils.unAuthorized(response, new Exception401("인증되지 않았습니다")); + filterResponseUtils.unAuthorized(response, new Exception401("인증되지 않았습니다")); }) ); // 9. 권한 실패 처리 http.exceptionHandling((exceptionHandling) -> exceptionHandling.accessDeniedHandler((request, response, accessDeniedException) -> { - FilterResponseUtils.forbidden(response, new Exception403("권한이 없습니다")); + filterResponseUtils.forbidden(response, new Exception403("권한이 없습니다")); }) ); @@ -90,20 +94,21 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti new AntPathRequestMatcher("/user/**"), new AntPathRequestMatcher("/portfolios/**"), new AntPathRequestMatcher("/chat/**"), - new AntPathRequestMatcher("/quotations/**") - ).authenticated() + new AntPathRequestMatcher("/quotations/**"), + new AntPathRequestMatcher("/payments/**") + ).authenticated() // 검증 필요 .requestMatchers( new AntPathRequestMatcher("/chat", "POST"), new AntPathRequestMatcher("/quotations/confirmAll/**", "POST") - ).hasRole("COUPLE") + ).hasRole("couple") .requestMatchers( new AntPathRequestMatcher("/portfolios", "POST"), new AntPathRequestMatcher("/portfolios", "PUT"), new AntPathRequestMatcher("/portfolios", "DELETE"), new AntPathRequestMatcher("/quotations/**", "PUT"), new AntPathRequestMatcher("/quotations/**", "POST") - ).hasRole("PLANNER") + ).hasRole("planner") .anyRequest().permitAll() ); diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/ApiUtils.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/ApiUtils.java index c60da19f..0eb713c1 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/ApiUtils.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/ApiUtils.java @@ -2,7 +2,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.Setter; import org.springframework.http.HttpStatus; public class ApiUtils { @@ -15,14 +14,14 @@ public static ApiResult error(String message, HttpStatus status) { return new ApiResult<>(false, null, new ApiError(message, status.value())); } - @Getter @Setter @AllArgsConstructor + @Getter @AllArgsConstructor public static class ApiResult { private final boolean success; private final T response; private final ApiError error; } - @Getter @Setter @AllArgsConstructor + @Getter @AllArgsConstructor public static class ApiError { private final String message; private final int status; diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/FilterResponseUtils.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/FilterResponseUtils.java index f174f518..2a1e776d 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/FilterResponseUtils.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/_core/utils/FilterResponseUtils.java @@ -1,25 +1,34 @@ package com.kakao.sunsuwedding._core.utils; +import com.fasterxml.jackson.databind.ObjectMapper; import com.kakao.sunsuwedding._core.errors.exception.Exception401; import com.kakao.sunsuwedding._core.errors.exception.Exception403; -import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import java.io.IOException; +@Component public class FilterResponseUtils { - public static void unAuthorized(HttpServletResponse resp, Exception401 e) throws IOException { + + private final ObjectMapper om; + + @Autowired + public FilterResponseUtils(ObjectMapper om) { + this.om = om; + } + + public void unAuthorized(HttpServletResponse resp, Exception401 e) throws IOException { resp.setStatus(e.status().value()); resp.setContentType("application/json; charset=utf-8"); - ObjectMapper om = new ObjectMapper(); String responseBody = om.writeValueAsString(e.body()); resp.getWriter().println(responseBody); } - public static void forbidden(HttpServletResponse resp, Exception403 e) throws IOException { + public void forbidden(HttpServletResponse resp, Exception403 e) throws IOException { resp.setStatus(e.status().value()); resp.setContentType("application/json; charset=utf-8"); - ObjectMapper om = new ObjectMapper(); String responseBody = om.writeValueAsString(e.body()); resp.getWriter().println(responseBody); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/Portfolio.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/Portfolio.java index 0015fd4a..bac17757 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/Portfolio.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/Portfolio.java @@ -73,44 +73,4 @@ public Portfolio(Long id, Planner planner, String title, String description, Str this.maxPrice = maxPrice; this.createdAt = (createdAt == null? LocalDateTime.now() : createdAt); } - - public void updateTitle(String title) { - this.title = title; - } - - public void updateDescription(String description) { - this.description = description; - } - - public void updateLocation(String location) { - this.location = location; - } - - public void updateCareer(String career) { - this.career = career; - } - - public void updatePartnerCompany(String partnerCompany) { - this.partnerCompany = partnerCompany; - } - - public void updateTotalPrice(Long totalPrice) { - this.totalPrice = totalPrice; - } - - public void updateContractCount(Long contractCount) { - this.contractCount = contractCount; - } - - public void updateAvgPrice(Long avgPrice) { - this.avgPrice = avgPrice; - } - - public void updateMinPrice(Long minPrice) { - this.minPrice = minPrice; - } - - public void updateMaxPrice(Long maxPrice) { - this.maxPrice = maxPrice; - } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioJPARepository.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioJPARepository.java index 787e570b..5c4ff599 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioJPARepository.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioJPARepository.java @@ -20,5 +20,5 @@ public interface PortfolioJPARepository extends JpaRepository { void deleteByPlanner(Planner planner); @Query("select p from Portfolio p where p.planner.id = :plannerId") - Optional findByPlannerId(@Param("plannerId") int plannerId); + Optional findByPlannerId(@Param("plannerId") Long plannerId); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java index a0842075..809b8e63 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java @@ -28,7 +28,7 @@ public ResponseEntity addPortfolios(@RequestPart PortfolioRequest.addDTO requ @RequestPart MultipartFile[] images, Error errors, @AuthenticationPrincipal CustomUserDetails userDetails) { - Pair info = portfolioService.addPortfolio(request, userDetails.getPlanner().getId()); + Pair info = portfolioService.addPortfolio(request, userDetails.getUser().getId()); imageItemService.uploadImage(images, info.getFirst(), info.getSecond()); return ResponseEntity.ok().body(ApiUtils.success(null)); @@ -52,7 +52,7 @@ public ResponseEntity updatePortfolios(@RequestPart PortfolioRequest.updateDT @RequestPart MultipartFile[] images, Error errors, @AuthenticationPrincipal CustomUserDetails userDetails) { - Pair info = portfolioService.updatePortfolio(request, userDetails.getPlanner().getId()); + Pair info = portfolioService.updatePortfolio(request, userDetails.getUser().getId()); // TODO: 이미지 업데이트 처리 imageItemService.updateImage(images, info.getFirst(), info.getSecond()); diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java index b04832f8..55517481 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java @@ -30,12 +30,14 @@ public class PortfolioService { private final PriceItemJPARepository priceItemJPARepository; private final PlannerJPARepository plannerJPARepository; - public Pair addPortfolio(PortfolioRequest.addDTO request, int plannerId) { + public Pair addPortfolio(PortfolioRequest.addDTO request, Long plannerId) { // 요청한 플래너 탐색 Planner planner = plannerJPARepository.findById(plannerId) .orElseThrow(() -> new Exception400("플래너를 찾을 수 없습니다: " + plannerId)); // TODO: 해당 플래너가 생성한 포트폴리오가 이미 있는 경우 예외처리 + portfolioJPARepository.findByPlannerId(plannerId) + .ifPresent(presentPortfolio -> new Exception400("해당 플래너의 포트폴리오가 이미 존재합니다: " + plannerId)); // 필요한 계산값 연산 Long totalPrice = request.getItems().stream() @@ -108,7 +110,7 @@ public PortfolioResponse.findById getPortfolioById(Long id) { } @Transactional - public Pair updatePortfolio(PortfolioRequest.updateDTO request, int plannerId) { + public Pair updatePortfolio(PortfolioRequest.updateDTO request, Long plannerId) { // 요청한 플래너 및 포트폴리오 탐색 Planner planner = plannerJPARepository.findById(plannerId) .orElseThrow(() -> new Exception400("플래너를 찾을 수 없습니다: " + plannerId)); @@ -128,18 +130,22 @@ public Pair updatePortfolio(PortfolioRequest.updateDTO reques .mapToLong(PortfolioRequest.updateDTO.ItemDTO::getItemPrice) .max(); - // 포트폴리오 변경사항 업데이트 - portfolio.updateTitle(request.getTitle()); - portfolio.updateDescription(request.getDescription()); - portfolio.updateLocation(request.getLocation()); - portfolio.updateCareer(request.getCareer()); - portfolio.updatePartnerCompany(request.getPartnerCompany()); - portfolio.updateTotalPrice(totalPrice); - portfolio.updateContractCount(contractCount); - portfolio.updateAvgPrice(avgPrice); - portfolio.updateMinPrice(minPrice.orElse(0)); - portfolio.updateMaxPrice(maxPrice.orElse(0)); - + // 불변 객체 패턴을 고려한 포트폴리오 변경사항 업데이트 + Portfolio updatedPortfolio = Portfolio.builder() + .id(portfolio.getId()) + .planner(planner) + .title(request.getTitle() != null ? request.getTitle() : portfolio.getTitle()) + .description(request.getDescription() != null ? request.getDescription() : portfolio.getDescription()) + .location(request.getLocation() != null ? request.getLocation() : portfolio.getLocation()) + .career(request.getCareer() != null ? request.getCareer() : portfolio.getCareer()) + .partnerCompany(request.getPartnerCompany() != null ? request.getPartnerCompany() : portfolio.getPartnerCompany()) + .totalPrice(totalPrice) + .contractCount(contractCount) + .avgPrice(avgPrice) + .minPrice(minPrice.orElse(0)) + .maxPrice(maxPrice.orElse(0)) + .build(); + portfolioJPARepository.save(updatedPortfolio); // 해당하는 가격 아이템 탐색 & 업데이트 List priceItemList = priceItemJPARepository.findByPortfolioId(portfolio.getId()); @@ -147,18 +153,24 @@ public Pair updatePortfolio(PortfolioRequest.updateDTO reques PriceItem priceItem = priceItemList.get(i); PortfolioRequest.updateDTO.ItemDTO item = request.getItems().get(i); - priceItem.updateItemTitle(item.getItemTitle()); - priceItem.updateItemPrice(item.getItemPrice()); + PriceItem updatedPriceItem = PriceItem.builder() + .id(priceItem.getId()) + .portfolio(portfolio) + .itemTitle(item.getItemTitle() != null ? item.getItemTitle() : priceItem.getItemTitle()) + .itemPrice(item.getItemPrice() != null ? item.getItemPrice() : priceItem.getItemPrice()) + .build(); + + priceItemJPARepository.save(updatedPriceItem); } // 이미지 처리 로직에 활용하기 위해 포트폴리오 객체 리턴 - return Pair.of(portfolio, planner); + return Pair.of(updatedPortfolio, planner); } @Transactional - public void deletePortfolio(Pair info) { - if (!info.getFirst().getRoleName().equals(Role.PLANNER.getRoleName())) { + public void deletePortfolio(Pair info) { + if (!info.getFirst().equals(Role.PLANNER.getRoleName())) { throw new Exception403(BaseException.PERMISSION_DENIED_METHOD_ACCESS.getMessage()); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageEncoder.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageEncoder.java index 075d6649..24f1dbe9 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageEncoder.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageEncoder.java @@ -11,7 +11,7 @@ public class ImageEncoder { public static String encode(ImageItem imageItem) { - Resource resource = new FileSystemResource(imageItem.getFilePath() + imageItem.getOriginFileName()); + Resource resource = new FileSystemResource(imageItem.getFilePath()); if (!resource.exists()) { throw new Exception404(BaseException.PORTFOLIO_IMAGE_NOT_FOUND.getMessage()); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItem.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItem.java index 6208a758..feb0bd4e 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItem.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItem.java @@ -50,14 +50,4 @@ public ImageItem(Long id, Portfolio portfolio, String originFileName, String fil this.fileSize = fileSize; this.thumbnail = thumbnail; } - - public void updateOriginFileName(String originFileName) { - this.originFileName = originFileName; - } - public void updateFilePath(String filePath) { - this.filePath = filePath; - } - public void updateFileSize(Long fileSize) { - this.fileSize = fileSize; - } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemJPARepository.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemJPARepository.java index c70597b9..c5e209ff 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemJPARepository.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemJPARepository.java @@ -22,5 +22,5 @@ public interface ImageItemJPARepository extends JpaRepository { @Query("delete from ImageItem i where i.portfolio.id = :portfolioId") void deleteAllByPortfolioId(@Param("portfolioId") Long portfolioId); - void deleteAllByPortfolioPlannerId(int id); + void deleteAllByPortfolioPlannerId(Long id); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemService.java index 6ff6bf4f..930d3970 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/image/ImageItemService.java @@ -1,6 +1,5 @@ package com.kakao.sunsuwedding.portfolio.image; -import com.kakao.sunsuwedding._core.errors.exception.Exception400; import com.kakao.sunsuwedding._core.errors.exception.Exception500; import com.kakao.sunsuwedding.portfolio.Portfolio; import com.kakao.sunsuwedding.user.planner.Planner; @@ -13,6 +12,9 @@ import org.springframework.web.multipart.MultipartFile; import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; import java.util.UUID; @Service @@ -23,107 +25,93 @@ public class ImageItemService { private final ImageItemJPARepository imageItemJPARepository; - public void uploadImage(MultipartFile[] images, Portfolio portfolio, Planner planner) { - - // 저장 경로 설정 + private String setDirectoryPath(int id, String username) { String separator = System.getProperty("file.separator"); String baseDirectory = System.getProperty("user.dir") + separator + "gallery" + separator; - String uploadDirectory = baseDirectory + planner.getUsername() + separator; + String uploadDirectory = baseDirectory + id + "_" + username + separator; + return uploadDirectory; + } + + private File makeDirectory(String uploadDirectory) { File directory = new File(uploadDirectory); if (!directory.exists()) { - boolean created = directory.mkdirs(); // 디렉토리가 존재하지 않으면 생성 + // 디렉토리가 존재하지 않으면 생성 + boolean created = directory.mkdirs(); if (!created) { // 디렉토리 생성에 실패한 경우 예외 처리 throw new Exception500("디렉토리 생성에 실패했습니다."); } } + return directory; + } + + private String makeImage(String uploadDirectory, MultipartFile image) { + try { + // 이미지 파일 생성 + String originalImageName = image.getOriginalFilename(); + String NameWithoutExtension = originalImageName.split("\\.")[0]; + String uploadImageName = UUID.randomUUID() + "(" + NameWithoutExtension + ")"; + String uploadImagePath = uploadDirectory + uploadImageName; + image.transferTo(new File(uploadImagePath)); + logger.debug("Trying to process image: {}", image.getOriginalFilename()); + + return uploadImagePath; + } + catch (IOException e) { + logger.error("Failed to process image", e); + throw new Exception500("이미지 처리에 실패했습니다."); + } + } + private void saveImage(Portfolio portfolio, MultipartFile image, MultipartFile[] images, String uploadImagePath){ + ImageItem imageItem = ImageItem.builder() + .portfolio(portfolio) + .originFileName(image.getOriginalFilename()) + .filePath(uploadImagePath) + .fileSize(image.getSize()) + .thumbnail(image == images[0]) + .build(); + imageItemJPARepository.save(imageItem); + } + + private void deleteImageFiles(File directory, Long portfolioId) { + // 이미지 파일을 Base64로 인코딩하고 나니까 확장자가 사라져서 + // 일단 그대로 디렉토리 내 파일 일괄 삭제하는 로직 + try { + FileUtils.cleanDirectory(directory); + } + catch (Exception e) {throw new Exception500("디렉토리 비우기에 실패하였습니다.");} + + // TODO: 삭제할 이미지 데이터가 존재하지 않는 경우 예외처리 + imageItemJPARepository.deleteAllByPortfolioId(portfolioId); + } + + public void uploadImage(MultipartFile[] images, Portfolio portfolio, Planner planner) { + // 저장 경로 설정 (root -> gallery -> {userId}_{username} 폴더) + String uploadDirectory = setDirectoryPath(planner.getId(), planner.getUsername()); + makeDirectory(uploadDirectory); // 이미지 생성 및 DB 저장 for (MultipartFile image : images) { - try { - String originalImageName = image.getOriginalFilename(); - String uploadImageName = UUID.randomUUID() + "_" + originalImageName; - String uploadImagePath = uploadDirectory + uploadImageName; - image.transferTo(new File(uploadImagePath)); - logger.debug("Trying to process image: {}", image.getOriginalFilename()); - - // TODO: Thumbnail인지 아닌지 확인하고 저장하는 로직 - - boolean thumbnail = false; - if (image == images[0]) { - thumbnail = true; - } - - ImageItem imageItem = ImageItem.builder() - .portfolio(portfolio) - .originFileName(originalImageName) - .filePath(uploadImagePath) - .fileSize(image.getSize()) - .thumbnail(thumbnail) - .build(); - imageItemJPARepository.save(imageItem); - } - catch (Exception e) { - logger.error("Failed to process image", e); - throw new Exception500("이미지 처리에 실패했습니다."); - } + String uploadImagePath = makeImage(uploadDirectory, image); + saveImage(portfolio, image, images, uploadImagePath); } } @Transactional public void updateImage(MultipartFile[] images, Portfolio portfolio, Planner planner) { - // 저장 경로 설정 - String separator = System.getProperty("file.separator"); - String baseDirectory = System.getProperty("user.dir") +separator + "gallery" + separator; - String uploadDirectory = baseDirectory + planner.getUsername() + separator; - - File directory = new File(uploadDirectory); - if (!directory.exists()) { - boolean created = directory.mkdirs(); // 디렉토리가 존재하지 않으면 생성 - if (!created) { - // 디렉토리 생성에 실패한 경우 예외 처리 - throw new Exception500("디렉토리 생성에 실패했습니다."); - } - } + // 저장 경로 설정 (root -> gallery -> {userId}_{username} 폴더) + String uploadDirectory = setDirectoryPath(planner.getId(), planner.getUsername()); + File directory = makeDirectory(uploadDirectory); // 기존의 서버 이미지 파일 및 DB 메타데이터 삭제 - try { - FileUtils.cleanDirectory(directory); - } - catch (Exception e) {throw new Exception500("디렉토리 비우기에 실패하였습니다.");} - // TODO: 삭제할 이미지 데이터가 존재하지 않는 경우 예외처리 - imageItemJPARepository.deleteAllByPortfolioId(portfolio.getId()); + deleteImageFiles(directory, portfolio.getId()); // 이미지 생성 및 DB 저장 for (MultipartFile image : images) { - try { - String originalImageName = image.getOriginalFilename(); - String uploadImageName = UUID.randomUUID() + "_" + originalImageName; - String uploadImagePath = uploadDirectory + uploadImageName; - image.transferTo(new File(uploadImagePath)); - logger.debug("Trying to process image: {}", image.getOriginalFilename()); - - // TODO: Thumbnail인지 아닌지 확인하고 저장하는 로직 - - boolean thumbnail = false; - if (image == images[0]) { - thumbnail = true; - } - - ImageItem imageItem = ImageItem.builder() - .portfolio(portfolio) - .originFileName(originalImageName) - .filePath(uploadImagePath) - .fileSize(image.getSize()) - .thumbnail(thumbnail) - .build(); - imageItemJPARepository.save(imageItem); - } - catch (Exception e) { - logger.error("Failed to process image", e); - throw new Exception500("이미지 처리에 실패했습니다."); - } + String uploadImagePath = makeImage(uploadDirectory, image); + saveImage(portfolio, image, images, uploadImagePath); } } + } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItem.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItem.java index 4e2d581d..5b0a866b 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItem.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItem.java @@ -43,10 +43,4 @@ public PriceItem(Long id, Portfolio portfolio, String itemTitle, Long itemPrice) this.itemPrice = itemPrice; } - public void updateItemTitle(String itemTitle) { - this.itemTitle = itemTitle; - } - public void updateItemPrice(Long itemPrice) { - this.itemPrice = itemPrice; - } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java index 0a57db40..b1132dc3 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java @@ -16,5 +16,5 @@ public interface PriceItemJPARepository extends JpaRepository { @EntityGraph("PriceItemWithPortfolioAndPlanner") List findAllByPortfolioId(Long id); - void deleteAllByPortfolioPlannerId(int id); + void deleteAllByPortfolioPlannerId(Long id); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRequest.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRequest.java index b4bff70a..02fe2f0e 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRequest.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRequest.java @@ -1,5 +1,6 @@ package com.kakao.sunsuwedding.user; +import com.kakao.sunsuwedding.user.base_user.User; import com.kakao.sunsuwedding.user.constant.Grade; import com.kakao.sunsuwedding.user.couple.Couple; import com.kakao.sunsuwedding.user.planner.Planner; @@ -9,6 +10,8 @@ import lombok.Getter; import lombok.Setter; +import java.time.LocalDateTime; + public class UserRequest { @Getter @Setter @@ -41,15 +44,18 @@ public Couple toCoupleEntity() { .password(password) .username(username) .grade(Grade.NORMAL) + .is_active(true) + .created_at(LocalDateTime.now()) .build(); } - public Planner toPlannerEntity() { return Planner.builder() .email(email) .password(password) .username(username) .grade(Grade.NORMAL) + .is_active(true) + .created_at(LocalDateTime.now()) .build(); } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserResponse.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserResponse.java index 731b9446..f9b426fd 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserResponse.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserResponse.java @@ -1,8 +1,7 @@ package com.kakao.sunsuwedding.user; +import com.kakao.sunsuwedding.user.base_user.User; import com.kakao.sunsuwedding.user.constant.Role; -import com.kakao.sunsuwedding.user.couple.Couple; -import com.kakao.sunsuwedding.user.planner.Planner; import lombok.Getter; import lombok.Setter; @@ -12,25 +11,20 @@ public class UserResponse { @Getter @Setter public static class FindById{ + private Long userId; private String username; private String email; private String role; private String grade; private String payedAt; - public FindById(Couple couple) { - this.username = couple.getUsername(); - this.email = couple.getEmail(); - this.role = Role.COUPLE.getRoleName(); - this.grade = couple.getGrade().getGradeName(); - this.payedAt = (couple.getPayedAt() == null) ? null : couple.getPayedAt().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); - } - public FindById(Planner planner) { - this.username = planner.getUsername(); - this.email = planner.getEmail(); - this.role = Role.PLANNER.getRoleName(); - this.grade = planner.getGrade().getGradeName(); - this.payedAt = (planner.getPayedAt() == null) ? null : planner.getPayedAt().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); + public FindById(User user) { + this.userId = user.getId(); + this.username = user.getUsername(); + this.email = user.getEmail(); + this.role = user.getDtype(); + this.grade = user.getGrade().getGradeName(); + this.payedAt = (user.getPayed_at() == null) ? null : user.getPayed_at().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); } } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRestController.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRestController.java index 35d5801b..4dcb4be2 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRestController.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserRestController.java @@ -3,6 +3,7 @@ import com.kakao.sunsuwedding._core.security.CustomUserDetails; import com.kakao.sunsuwedding._core.security.JWTProvider; import com.kakao.sunsuwedding._core.utils.ApiUtils; +import com.kakao.sunsuwedding.user.base_user.User; import com.kakao.sunsuwedding.user.constant.Role; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -35,24 +36,14 @@ public ResponseEntity login(@RequestBody @Valid UserRequest.LoginDTO requestD // 유저 정보 조회 @GetMapping("/info") public ResponseEntity getUserInfo(@AuthenticationPrincipal CustomUserDetails userDetails) { - Pair info = userDetails.getInfo(); - UserResponse.FindById responseDTO = userService.findById(info.getFirst(), info.getSecond()); + UserResponse.FindById responseDTO = userService.findById(userDetails.getUser().getId()); return ResponseEntity.ok().body(ApiUtils.success(responseDTO)); } - // 유저 등급 업그레이드 - @PostMapping("/upgrade") - public ResponseEntity upgrade(@AuthenticationPrincipal CustomUserDetails userDetails) { - Pair info = userDetails.getInfo(); - userService.upgrade(info.getFirst(), info.getSecond()); - return ResponseEntity.ok().body(ApiUtils.success(null)); - } - // 회원 탈퇴 @DeleteMapping("") public ResponseEntity withdraw(@AuthenticationPrincipal CustomUserDetails userDetails) { - Pair info = userDetails.getInfo(); - userService.withdraw(info.getFirst(),info.getSecond()); + userService.withdraw(userDetails.getUser().getId()); return ResponseEntity.ok().body(ApiUtils.success(null)); } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserService.java index 98396800..64e51c1c 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/UserService.java @@ -5,13 +5,14 @@ import com.kakao.sunsuwedding._core.errors.exception.Exception404; import com.kakao.sunsuwedding._core.errors.exception.Exception500; import com.kakao.sunsuwedding._core.security.JWTProvider; +import com.kakao.sunsuwedding.user.base_user.User; +import com.kakao.sunsuwedding.user.base_user.UserJPARepository; import com.kakao.sunsuwedding.user.constant.Grade; import com.kakao.sunsuwedding.user.constant.Role; -import com.kakao.sunsuwedding.user.couple.Couple; import com.kakao.sunsuwedding.user.couple.CoupleJPARepository; -import com.kakao.sunsuwedding.user.planner.Planner; import com.kakao.sunsuwedding.user.planner.PlannerJPARepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,8 +23,10 @@ @Transactional(readOnly = true) @RequiredArgsConstructor @Service +@Slf4j public class UserService { private final PasswordEncoder passwordEncoder; + private final UserJPARepository userJPARepository; private final CoupleJPARepository coupleJPARepository; private final PlannerJPARepository plannerJPARepository; @@ -32,7 +35,7 @@ public void signup(UserRequest.SignUpDTO requestDTO) { sameCheckEmail(requestDTO.getEmail()); sameCheckPassword(requestDTO.getPassword(), requestDTO.getPassword2()); requestDTO.setPassword(passwordEncoder.encode(requestDTO.getPassword())); - Role role = getRole(requestDTO.getRole()); + Role role = Role.valueOfRole(requestDTO.getRole()); try { if (role == Role.COUPLE) { coupleJPARepository.save(requestDTO.toCoupleEntity()); @@ -46,78 +49,25 @@ public void signup(UserRequest.SignUpDTO requestDTO) { } public String login(UserRequest.LoginDTO requestDTO) { - // 예비 부부 - Optional couplePS = coupleJPARepository.findByEmail(requestDTO.getEmail()); - if (couplePS.isPresent()) { - Couple couple = couplePS.get(); - if (!passwordEncoder.matches(requestDTO.getPassword(), couple.getPassword())) { - throw new Exception400(BaseException.USER_PASSWORD_WRONG.getMessage()); - } - return JWTProvider.create(couple); - } - // 플래너 - Optional plannerPS = plannerJPARepository.findByEmail(requestDTO.getEmail()); - if (plannerPS.isPresent()) { - Planner planner = plannerPS.get(); - if (!passwordEncoder.matches(requestDTO.getPassword(), planner.getPassword())) { - throw new Exception400(BaseException.USER_PASSWORD_WRONG.getMessage()); - } - return JWTProvider.create(planner); + User user = userJPARepository.findByEmail(requestDTO.getEmail()).orElseThrow( + () -> new Exception400(BaseException.USER_EMAIL_NOT_FOUND.getMessage() + requestDTO.getEmail()) + ); + log.debug("디버그: 로그인 토큰 {}", user.getId()); + if (!passwordEncoder.matches(requestDTO.getPassword(), user.getPassword())) { + throw new Exception400(BaseException.USER_PASSWORD_WRONG.getMessage()); } - // 존재하지 않는 이메일 - throw new Exception400(BaseException.USER_EMAIL_NOT_FOUND.getMessage() + requestDTO.getEmail()); + return JWTProvider.create(user); } - public UserResponse.FindById findById(Role role, int id) { - if (role == Role.PLANNER){ - return new UserResponse.FindById(getPlanner(id)); - } - else { - return new UserResponse.FindById(getCouple(id)); - } - } - - // 유저 등급 업그레이드 - @Transactional - public void upgrade(Role role, int id) { - if (role == Role.PLANNER){ - Planner planner = getPlanner(id); - if (planner.getGrade() == Grade.PREMIUM){ - throw new Exception400(BaseException.USER_ALREADY_PREMIUM.getMessage()); - } - planner.upgrade(); - } - else { - Couple couple = getCouple(id); - if (couple.getGrade() == Grade.PREMIUM){ - throw new Exception400(BaseException.USER_ALREADY_PREMIUM.getMessage()); - } - couple.upgrade(); - } + public UserResponse.FindById findById(Long userId) { + return new UserResponse.FindById(findUserById(userId)); } // 회원 탈퇴 @Transactional - public void withdraw(Role role, int id) { - // -- 플래너 회원 탈퇴 -- - if (role == Role.PLANNER){ - Planner planner = getPlanner(id); - plannerJPARepository.deleteById(id); - - } - // -- 예비 부부 회원 탈퇴 -- - else { - Couple couple = getCouple(id); - coupleJPARepository.deleteById(id); - } - } - - private Role getRole(String roleName) { - Role role = Role.valueOfRole(roleName); - if (role == null){ - throw new Exception400(BaseException.USER_ROLE_WRONG.getMessage()); - } - else return role; + public void withdraw(Long userId) { + findUserById(userId); + userJPARepository.deleteById(userId); } private void sameCheckPassword(String password, String password2) { @@ -128,21 +78,14 @@ private void sameCheckPassword(String password, String password2) { } private void sameCheckEmail(String email) { - Optional couple = coupleJPARepository.findByEmail(email); - Optional planner = plannerJPARepository.findByEmail(email); - - if (couple.isPresent() || planner.isPresent()) { + Optional userOptional = userJPARepository.findByEmailNative(email); + if (userOptional.isPresent()){ throw new Exception400(BaseException.USER_EMAIL_EXIST.getMessage()); } } - private Planner getPlanner(int id){ - return plannerJPARepository.findById(id).orElseThrow( - () -> new Exception404(BaseException.USER_NOT_FOUND.getMessage()) - ); - } - private Couple getCouple(int id){ - return coupleJPARepository.findById(id).orElseThrow( + private User findUserById(Long userId){ + return userJPARepository.findById(userId).orElseThrow( () -> new Exception404(BaseException.USER_NOT_FOUND.getMessage()) ); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/base_user/User.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/base_user/User.java new file mode 100644 index 00000000..cc012f7a --- /dev/null +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/base_user/User.java @@ -0,0 +1,74 @@ +package com.kakao.sunsuwedding.user.base_user; + +import com.kakao.sunsuwedding.user.constant.Grade; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Entity +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name = "dtype") +@Where(clause = "is_active = true") +@Table(name="user_tb") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 100, nullable = false, unique = true) + private String email; + + @Column(length = 256, nullable = false) + private String password; + + @Column(length = 45, nullable = false) + private String username; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private Grade grade; + + @Column + private boolean is_active; + + @Column(nullable = false) + private LocalDateTime created_at; + + // 결제와 관련된 필드 + @Column(length = 256) + private String order_id; + + @Column + private Long payed_amount; + + @Column + private LocalDateTime payed_at; + + // 유저 등급 업그레이드 + public void upgrade() { + this.grade = Grade.PREMIUM; + this.payed_at = LocalDateTime.now(); + } + + // 결제 정보 저장 (주문 번호, 금액) + public void savePaymentInfo(String order_id, Long payed_amount){ + this.order_id = order_id; + this.payed_amount = payed_amount; + } + + // planner인지 couple인지 @DiscriminatorColumn의 내용을 보고 알려줌 + @Transient + public String getDtype(){ + DiscriminatorValue val = this.getClass().getAnnotation( DiscriminatorValue.class ); + return val == null ? null : val.value(); + } +} \ No newline at end of file diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/base_user/UserJPARepository.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/base_user/UserJPARepository.java new file mode 100644 index 00000000..809dcdb4 --- /dev/null +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/base_user/UserJPARepository.java @@ -0,0 +1,17 @@ +package com.kakao.sunsuwedding.user.base_user; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface UserJPARepository extends JpaRepository { + // where is_active = true가 적용되지 않음 + // 이메일 중복 비교를 위해 사용 + @Query(value = "select u.* from user_tb u where u.email = :email", nativeQuery = true) + Optional findByEmailNative(@Param("email") String email); + + // where is_active = true가 적용됨 + Optional findByEmail(String email); +} diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/constant/Role.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/constant/Role.java index fbbb7da2..15bf4ad3 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/constant/Role.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/constant/Role.java @@ -1,5 +1,7 @@ package com.kakao.sunsuwedding.user.constant; +import com.kakao.sunsuwedding._core.errors.BaseException; +import com.kakao.sunsuwedding._core.errors.exception.Exception400; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -17,6 +19,6 @@ public static Role valueOfRole(String role) { return Arrays.stream(values()) .filter(value -> value.roleName.equals(role)) .findAny() - .orElse(null); + .orElseThrow(() -> new Exception400(BaseException.USER_ROLE_WRONG.getMessage())); } } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/Couple.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/Couple.java index 2a2215a8..fcbe5dfd 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/Couple.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/Couple.java @@ -1,56 +1,26 @@ package com.kakao.sunsuwedding.user.couple; +import com.kakao.sunsuwedding.user.base_user.User; import com.kakao.sunsuwedding.user.constant.Grade; -import jakarta.persistence.*; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; import java.time.LocalDateTime; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -@Table(name="couple_tb") -public class Couple { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; - - @Column(length = 100, nullable = false, unique = true) - private String email; - - @Column(length = 256, nullable = false) - private String password; - - @Column(length = 45, nullable = false) - private String username; - - @Column(nullable = false) - private LocalDateTime createdAt; - - @Column - private LocalDateTime payedAt; - - @Column(nullable = false) - @Enumerated(EnumType.STRING) - private Grade grade; +@DiscriminatorValue(value = "couple") +@SQLDelete(sql = "UPDATE user_tb SET is_active = false WHERE id = ?") +public class Couple extends User { @Builder - public Couple(int id, String email, String password, String username, LocalDateTime payedAt, Grade grade) { - this.id = id; - this.email = email; - this.password = password; - this.username = username; - this.payedAt = payedAt; - this.grade = grade; - this.createdAt = LocalDateTime.now(); - } - - // 유저 등급 업그레이드 - public void upgrade() { - this.grade = Grade.PREMIUM; - this.payedAt = LocalDateTime.now(); + public Couple(Long id, String email, String password, String username, Grade grade, boolean is_active, LocalDateTime created_at, String order_id, Long payed_amount, LocalDateTime payed_at) { + super(id, email, password, username, grade, is_active, created_at, order_id, payed_amount, payed_at); } } \ No newline at end of file diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/CoupleJPARepository.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/CoupleJPARepository.java index 23e8c6e9..0c55794d 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/CoupleJPARepository.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/couple/CoupleJPARepository.java @@ -2,8 +2,6 @@ import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; +public interface CoupleJPARepository extends JpaRepository { -public interface CoupleJPARepository extends JpaRepository { - Optional findByEmail(String email); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentRequest.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentRequest.java new file mode 100644 index 00000000..866de14f --- /dev/null +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentRequest.java @@ -0,0 +1,35 @@ +package com.kakao.sunsuwedding.user.payment; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; + +public class PaymentRequest { + + @Getter @Setter + public static class SaveDTO{ + @NotEmpty(message = "orderId는 비어있으면 안됩니다.") + private String orderId; + + private Long amount; + } + + @Getter @Setter + public static class ConfirmDTO{ + @NotEmpty(message = "orderId는 비어있으면 안됩니다.") + private String orderId; + + private Long amount; + } + + @Getter @Setter + public static class UpgradeDTO{ + @NotEmpty(message = "orderId는 비어있으면 안됩니다.") + private String orderId; + + @NotEmpty(message = "상태값은 비어있으면 안됩니다.") + private String status; + + private Long amount; + } +} diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentRestController.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentRestController.java new file mode 100644 index 00000000..6bdc9327 --- /dev/null +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentRestController.java @@ -0,0 +1,50 @@ +package com.kakao.sunsuwedding.user.payment; + +import com.kakao.sunsuwedding._core.security.CustomUserDetails; +import com.kakao.sunsuwedding._core.utils.ApiUtils; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/payments") +public class PaymentRestController { + + private final PaymentService paymentService; + + // 결제 정보 저장 (프론트 결제 요청 전) + @PostMapping("/save") + public ResponseEntity save( + @RequestBody @Valid PaymentRequest.SaveDTO requestDTO, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + paymentService.save(userDetails.getUser().getId(), requestDTO); + return ResponseEntity.ok().body(ApiUtils.success(null)); + } + + // 결제 정보 검증 (프론트 결제 요청 후) + @PostMapping("/confirm") + public ResponseEntity confirm( + @RequestBody @Valid PaymentRequest.ConfirmDTO requestDTO, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + String response = paymentService.confirm(userDetails.getUser().getId(), requestDTO); + return ResponseEntity.ok().body(ApiUtils.success(response)); + } + + // 유저 업그레이드 (프론트 결제 승인 후) + @PostMapping("/upgrade") + public ResponseEntity upgrade( + @RequestBody @Valid PaymentRequest.UpgradeDTO requestDTO, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + String response = paymentService.upgrade(userDetails.getUser().getId(), requestDTO); + return ResponseEntity.ok().body(ApiUtils.success(response)); + } +} diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentService.java new file mode 100644 index 00000000..194eee43 --- /dev/null +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/payment/PaymentService.java @@ -0,0 +1,64 @@ +package com.kakao.sunsuwedding.user.payment; + +import com.kakao.sunsuwedding._core.errors.BaseException; +import com.kakao.sunsuwedding._core.errors.exception.Exception400; +import com.kakao.sunsuwedding._core.errors.exception.Exception404; +import com.kakao.sunsuwedding.user.base_user.User; +import com.kakao.sunsuwedding.user.base_user.UserJPARepository; +import com.kakao.sunsuwedding.user.constant.Grade; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; + +@Transactional(readOnly = true) +@RequiredArgsConstructor +@Service +public class PaymentService { + + private final UserJPARepository userJPARepository; + + // 결제와 관련된 정보를 user에 저장함 + @Transactional + public void save(Long userId, PaymentRequest.SaveDTO requestDTO){ + User user = findUserById(userId); + user.savePaymentInfo(requestDTO.getOrderId(), requestDTO.getAmount()); + } + + // 검증에 성공하면 success, 실패하면 fail 을 반환 + public String confirm(Long userId, PaymentRequest.ConfirmDTO requestDTO){ + User user = findUserById(userId); + boolean isOK = isCorrectData(user, requestDTO.getOrderId(), requestDTO.getAmount()); + + return isOK ? "success" : "fail"; + } + + // 유저 등급을 NORMAL -> PREMIUM으로 업그레이드 시켜줌 + @Transactional + public String upgrade(Long userId, PaymentRequest.UpgradeDTO requestDTO) { + User user = findUserById(userId); + boolean isOK = isCorrectData(user, requestDTO.getOrderId(), requestDTO.getAmount()) + && requestDTO.getStatus().equals("DONE"); + + if (isOK) user.upgrade(); + return isOK ? "success" : "fail"; + } + + // 받아온 payment와 관련된 데이터(orderId, amount)가 정확한지 확인) + private boolean isCorrectData(User user, String orderId, Long amount){ + return user.getOrder_id().equals(orderId) + && Objects.equals(user.getPayed_amount(), amount); + } + + private User findUserById(Long userId){ + User user = userJPARepository.findById(userId).orElseThrow( + () -> new Exception404(BaseException.USER_NOT_FOUND.getMessage()) + ); + // 이미 프리미엄 등급인 경우 결제하면 안되므로 에러 던짐 + if (user.getGrade() == Grade.PREMIUM){ + throw new Exception400(BaseException.USER_ALREADY_PREMIUM.getMessage()); + } + return user; + } +} diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/Planner.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/Planner.java index 0ba7b877..8d406657 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/Planner.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/Planner.java @@ -1,56 +1,26 @@ package com.kakao.sunsuwedding.user.planner; +import com.kakao.sunsuwedding.user.base_user.User; import com.kakao.sunsuwedding.user.constant.Grade; -import jakarta.persistence.*; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; import java.time.LocalDateTime; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -@Table(name="planner_tb") -public class Planner { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; - - @Column(length = 100, nullable = false, unique = true) - private String email; - - @Column(length = 256, nullable = false) - private String password; - - @Column(length = 45, nullable = false) - private String username; - - @Column(nullable = false) - private LocalDateTime createdAt; - - @Column - private LocalDateTime payedAt; - - @Column(nullable = false) - @Enumerated(EnumType.STRING) - private Grade grade; +@DiscriminatorValue(value = "planner") +@SQLDelete(sql = "UPDATE user_tb SET is_active = false WHERE id = ?") +public class Planner extends User { @Builder - public Planner(int id, String email, String password, String username, LocalDateTime payedAt, Grade grade) { - this.id = id; - this.email = email; - this.password = password; - this.username = username; - this.payedAt = payedAt; - this.grade = grade; - this.createdAt = LocalDateTime.now(); - } - - // 유저 등급 업그레이드 - public void upgrade() { - this.grade = Grade.PREMIUM; - this.payedAt = LocalDateTime.now(); + public Planner(Long id, String email, String password, String username, Grade grade, boolean is_active, LocalDateTime created_at, String order_id, Long payed_amount, LocalDateTime payed_at) { + super(id, email, password, username, grade, is_active, created_at, order_id, payed_amount, payed_at); } } \ No newline at end of file diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/PlannerJPARepository.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/PlannerJPARepository.java index 28b227fb..19fcf8e3 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/PlannerJPARepository.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/user/planner/PlannerJPARepository.java @@ -2,8 +2,6 @@ import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; +public interface PlannerJPARepository extends JpaRepository { -public interface PlannerJPARepository extends JpaRepository { - Optional findByEmail(String email); } diff --git a/sunsu-wedding/src/main/resources/db/teardown.sql b/sunsu-wedding/src/main/resources/db/teardown.sql index dc29c98f..5deb84dd 100644 --- a/sunsu-wedding/src/main/resources/db/teardown.sql +++ b/sunsu-wedding/src/main/resources/db/teardown.sql @@ -1,6 +1,5 @@ SET REFERENTIAL_INTEGRITY FALSE; -truncate table planner_tb; -truncate table couple_tb; +truncate table user_tb; truncate table portfolio_tb; truncate table imageitem_tb; truncate table priceitem_tb; @@ -9,15 +8,15 @@ truncate table quotation_tb; SET REFERENTIAL_INTEGRITY TRUE; -- planner 비밀번호 : planner1234! -INSERT INTO planner_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`) VALUES ('1','planner@gmail.com','{bcrypt}$2a$10$89SwVjyXVDhK3GFcN4c8Bu3kQlNiWqjaTvgiXaCi9D/1eWx2w7CBa','planner','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','NORMAL'); -INSERT INTO planner_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`) VALUES ('2','planner2@gmail.com','{bcrypt}$2a$10$89SwVjyXVDhK3GFcN4c8Bu3kQlNiWqjaTvgiXaCi9D/1eWx2w7CBa','planner2','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','PREMIUM'); +INSERT INTO user_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`, `is_active`, `dtype`) VALUES ('2','planner@gmail.com','{bcrypt}$2a$10$89SwVjyXVDhK3GFcN4c8Bu3kQlNiWqjaTvgiXaCi9D/1eWx2w7CBa','planner','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','NORMAL', 'true', 'planner'); +INSERT INTO user_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`, `is_active`, `dtype`) VALUES ('3','planner2@gmail.com','{bcrypt}$2a$10$89SwVjyXVDhK3GFcN4c8Bu3kQlNiWqjaTvgiXaCi9D/1eWx2w7CBa','planner2','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','PREMIUM', 'true', 'planner'); -- JPA 테스트 할 때 DummyEntity가 생겨서 전체 테스트 시 PRIMARY_KEY_VIOLATION 에러나서 일단 id 2,3으로 해뒀습니다! -- couple 비밀번호 : couple1234! -INSERT INTO couple_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`) VALUES ('2','couple@gmail.com','{bcrypt}$2a$10$bKgX34po45/xYw1Dd8C81OYW4dkkVQV5lHd7a.06m1gBX689XERA.','couple','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','NORMAL'); -INSERT INTO couple_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`) VALUES ('3','couple2@gmail.com','{bcrypt}$2a$10$bKgX34po45/xYw1Dd8C81OYW4dkkVQV5lHd7a.06m1gBX689XERA.','couple2','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','PREMIUM'); +INSERT INTO user_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`, `is_active`, `dtype`) VALUES ('4','couple@gmail.com','{bcrypt}$2a$10$bKgX34po45/xYw1Dd8C81OYW4dkkVQV5lHd7a.06m1gBX689XERA.','couple','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','NORMAL', 'true', 'couple'); +INSERT INTO user_tb (`id`,`email`,`password`,`username`,`created_at`,`payed_at`,`grade`, `is_active`, `dtype`) VALUES ('5','couple2@gmail.com','{bcrypt}$2a$10$bKgX34po45/xYw1Dd8C81OYW4dkkVQV5lHd7a.06m1gBX689XERA.','couple2','2023-09-16 01:06:55.00','2023-09-20 15:26:55.00','PREMIUM', 'true', 'couple'); -INSERT INTO portfolio_tb (`id`, `planner_id`, `title`, `description`, `location`, `career`, `partner_company`, `total_price`, `contract_count`, `avg_price`, `min_price`, `max_price`, `created_at`) VALUES ('1', '1', 'test1', 'test1', '부산', 'none', 'none', '1000000', '10', '1000000', '1000000', '1000000', '2023-09-15 15:26:55.00'); -INSERT INTO portfolio_tb (`id`, `planner_id`, `title`, `description`, `location`, `career`, `partner_company`, `total_price`, `contract_count`, `avg_price`, `min_price`, `max_price`, `created_at`) VALUES ('2', '2', 'test2', 'test2', '부산', 'none', 'none', '2000000', '20', '2000000', '2000000', '2000000', '2023-09-20 15:26:55.00'); +INSERT INTO portfolio_tb (`id`, `planner_id`, `title`, `description`, `location`, `career`, `partner_company`, `total_price`, `contract_count`, `avg_price`, `min_price`, `max_price`, `created_at`) VALUES ('1', '3', 'test1', 'test1', '부산', 'none', 'none', '1000000', '10', '1000000', '1000000', '1000000', '2023-09-15 15:26:55.00'); +INSERT INTO portfolio_tb (`id`, `planner_id`, `title`, `description`, `location`, `career`, `partner_company`, `total_price`, `contract_count`, `avg_price`, `min_price`, `max_price`, `created_at`) VALUES ('2', '4', 'test2', 'test2', '부산', 'none', 'none', '2000000', '20', '2000000', '2000000', '2000000', '2023-09-20 15:26:55.00'); INSERT INTO priceitem_tb (`id`, `portfolio_id`, `item_title`, `item_price`) VALUES ('1', '1', '스튜디오1', '500000'); INSERT INTO priceitem_tb (`id`, `portfolio_id`, `item_title`, `item_price`) VALUES ('2', '1', '드레스1', '300000'); diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/_core/DummyEntity.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/_core/DummyEntity.java index b179c7ac..01fb6ef9 100644 --- a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/_core/DummyEntity.java +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/_core/DummyEntity.java @@ -3,26 +3,31 @@ import com.kakao.sunsuwedding.user.constant.Grade; import com.kakao.sunsuwedding.user.couple.Couple; import com.kakao.sunsuwedding.user.planner.Planner; +import org.springframework.cglib.core.Local; import org.springframework.security.core.userdetails.User; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import java.time.LocalDateTime; + public class DummyEntity { protected Couple newCouple(String username){ - PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return Couple.builder() .email(username+"@nate.com") - .password(passwordEncoder.encode("couple1234!")) + .password("couple1234!") .username("couple") + .created_at(LocalDateTime.now()) + .is_active(true) .grade(Grade.NORMAL) .build(); } protected Planner newPlanner(String username){ - PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return Planner.builder() .email(username+"@nate.com") - .password(passwordEncoder.encode("planner1234!")) + .password("planner1234!") .username("planner") + .created_at(LocalDateTime.now()) + .is_active(true) .grade(Grade.NORMAL) .build(); } diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioRepositoryTest.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioRepositoryTest.java index 6d38da06..146eae28 100644 --- a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioRepositoryTest.java +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioRepositoryTest.java @@ -19,7 +19,7 @@ public PortfolioRepositoryTest(@Autowired PortfolioJPARepository portfolioJPARep this.portfolioJPARepository = portfolioJPARepository; this.entityManager = entityManager; } - +/* @BeforeEach void setUp() { portfolioJPARepository.save(Portfolio.builder().title("test1").build()); @@ -86,5 +86,5 @@ void repositoryDeleteTest() { // then assertThat(portfolioJPARepository.count()).isEqualTo(previous_count - 1); - } + }*/ } diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/CoupleJPARepositoryTest.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/CoupleJPARepositoryTest.java index 106283de..c750fc46 100644 --- a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/CoupleJPARepositoryTest.java +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/CoupleJPARepositoryTest.java @@ -4,13 +4,13 @@ import com.kakao.sunsuwedding.user.couple.Couple; import com.kakao.sunsuwedding.user.couple.CoupleJPARepository; import jakarta.persistence.EntityManager; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.security.crypto.bcrypt.BCrypt; + +import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest public class CoupleJPARepositoryTest extends DummyEntity { @@ -26,23 +26,23 @@ public void setUp(){ coupleJPARepository.save(newCouple("ssar")); } - @DisplayName("이메일 찾기 - 성공") + @DisplayName("사용자 id로 찾기 - 성공") @Test - public void findByEmail_success_test() { + public void findById_success_test() { // given - String email = "ssar@nate.com"; + Long userId = 1L; // when - Couple couplePS = coupleJPARepository.findByEmail(email).orElseThrow( - () -> new RuntimeException("해당 이메일을 찾을 수 없습니다.") + Couple couple = coupleJPARepository.findById(userId).orElseThrow( + () -> new RuntimeException("해당 사용자를 찾을 수 없습니다.") ); // then (상태 검사) - Assertions.assertThat(couplePS.getId()).isEqualTo(1); - Assertions.assertThat(couplePS.getEmail()).isEqualTo("ssar@nate.com"); - Assertions.assertThat(BCrypt.checkpw("couple1234!", couplePS.getPassword())).isEqualTo(true); - Assertions.assertThat(couplePS.getUsername()).isEqualTo("couple"); - Assertions.assertThat(couplePS.getGrade().getGradeName()).isEqualTo("normal"); + assertThat(couple.getId()).isEqualTo(1); + assertThat(couple.getEmail()).isEqualTo("ssar@nate.com"); + assertThat(couple.getPassword()).isEqualTo("couple1234!"); + assertThat(couple.getUsername()).isEqualTo("couple"); + assertThat(couple.getGrade().getGradeName()).isEqualTo("normal"); } } diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/PlannerJPARepositoryTest.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/PlannerJPARepositoryTest.java index e22fabce..275673ef 100644 --- a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/PlannerJPARepositoryTest.java +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/PlannerJPARepositoryTest.java @@ -1,17 +1,16 @@ package com.kakao.sunsuwedding.user; import com.kakao.sunsuwedding._core.DummyEntity; -import com.kakao.sunsuwedding.user.couple.Couple; import com.kakao.sunsuwedding.user.planner.Planner; import com.kakao.sunsuwedding.user.planner.PlannerJPARepository; import jakarta.persistence.EntityManager; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.security.crypto.bcrypt.BCrypt; + +import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest public class PlannerJPARepositoryTest extends DummyEntity { @@ -27,23 +26,23 @@ public void setUp(){ plannerJPARepository.save(newPlanner("ssar")); } - @DisplayName("이메일 찾기 - 성공") + @DisplayName("사용자 id로 찾기 - 성공") @Test - public void findByEmail_success_test() { + public void findById_success_test() { // given - String email = "ssar@nate.com"; + Long userId = 1L; // when - Planner plannerPS = plannerJPARepository.findByEmail(email).orElseThrow( - () -> new RuntimeException("해당 이메일을 찾을 수 없습니다.") + Planner planner = plannerJPARepository.findById(userId).orElseThrow( + () -> new RuntimeException("해당 플래너를 찾을 수 없습니다.") ); // then (상태 검사) - Assertions.assertThat(plannerPS.getId()).isEqualTo(1); - Assertions.assertThat(plannerPS.getEmail()).isEqualTo("ssar@nate.com"); - Assertions.assertThat(BCrypt.checkpw("planner1234!", plannerPS.getPassword())).isEqualTo(true); - Assertions.assertThat(plannerPS.getUsername()).isEqualTo("planner"); - Assertions.assertThat(plannerPS.getGrade().getGradeName()).isEqualTo("normal"); + assertThat(planner.getId()).isEqualTo(1); + assertThat(planner.getEmail()).isEqualTo("ssar@nate.com"); + assertThat(planner.getPassword()).isEqualTo("planner1234!"); + assertThat(planner.getUsername()).isEqualTo("planner"); + assertThat(planner.getGrade().getGradeName()).isEqualTo("normal"); } } diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/RoleTest.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/RoleTest.java index 642c79cb..bf7e020e 100644 --- a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/RoleTest.java +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/RoleTest.java @@ -1,29 +1,33 @@ package com.kakao.sunsuwedding.user; +import com.kakao.sunsuwedding._core.errors.exception.Exception400; import com.kakao.sunsuwedding.user.constant.Role; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class RoleTest { @Test - public void planner_role_test() throws Exception { + public void planner_role_test() { String roleName = "planner"; Role result = Role.valueOfRole(roleName); Assertions.assertThat(result).isEqualTo(Role.PLANNER); } @Test - public void couple_role_test() throws Exception { + public void couple_role_test() { String roleName = "couple"; Role result = Role.valueOfRole(roleName); Assertions.assertThat(result).isEqualTo(Role.COUPLE); } @Test - public void null_role_test() throws Exception { + public void null_role_test() { String roleName = "asdf"; - Role result = Role.valueOfRole(roleName); - Assertions.assertThat(result).isEqualTo(null); + assertThrows(Exception400.class, () -> { + Role.valueOfRole(roleName); + }); } } diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/UserJPARepositoryTest.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/UserJPARepositoryTest.java new file mode 100644 index 00000000..474dce74 --- /dev/null +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/UserJPARepositoryTest.java @@ -0,0 +1,49 @@ +package com.kakao.sunsuwedding.user; + +import com.kakao.sunsuwedding._core.DummyEntity; +import com.kakao.sunsuwedding.user.base_user.User; +import com.kakao.sunsuwedding.user.base_user.UserJPARepository; +import com.kakao.sunsuwedding.user.couple.Couple; +import com.kakao.sunsuwedding.user.couple.CoupleJPARepository; +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class UserJPARepositoryTest extends DummyEntity { + + @Autowired + private UserJPARepository userJPARepository; + + @Autowired + private EntityManager em; + + @BeforeEach + public void setUp(){ + userJPARepository.save(newCouple("zxcv")); + } + + @DisplayName("사용자 id로 찾기 - 성공") + @Test + public void findById_success_test() { + // given + Long userId = 1L; + // when + User user = userJPARepository.findById(userId).orElseThrow( + () -> new RuntimeException("해당 유저를 찾을 수 없습니다.") + ); + + // then (상태 검사) + assertThat(user.getId()).isEqualTo(1); + assertThat(user.getEmail()).isEqualTo("zxcv@nate.com"); + assertThat(user.getPassword()).isEqualTo("couple1234!"); + assertThat(user.getUsername()).isEqualTo("couple"); + assertThat(user.getGrade().getGradeName()).isEqualTo("normal"); + } + +} diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/UserRestControllerTest.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/UserRestControllerTest.java index 932afb4e..1c5d02dd 100644 --- a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/UserRestControllerTest.java +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/user/UserRestControllerTest.java @@ -145,6 +145,7 @@ public void user_login_success_test() throws Exception { .content(requestBody) .contentType(MediaType.APPLICATION_JSON) ); + String responseBody = result.andReturn().getResponse().getContentAsString(); String responseHeader = result.andReturn().getResponse().getHeader(JWTProvider.HEADER); logger.debug("테스트 : " + responseBody); @@ -204,6 +205,7 @@ public void user_login_fail_wrong_password_test() throws Exception { result.andExpect(jsonPath("$.error.message").value("패스워드를 잘못 입력하셨습니다")); } + /* // ============ 회원 탈퇴 테스트 ============ /* @DisplayName("회원 탈퇴 성공 테스트") @@ -223,5 +225,5 @@ public void user_withdraw_success_test() throws Exception { // then result.andExpect(MockMvcResultMatchers.jsonPath("$.success").value("true")); } - */ + */ }