diff --git a/build.gradle b/build.gradle index 1110e796..eda665b4 100644 --- a/build.gradle +++ b/build.gradle @@ -32,14 +32,15 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' testImplementation 'org.springframework.security:spring-security-test' - // jwt - implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0' + // JWT Token + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' implementation 'org.springframework.boot:spring-boot-devtools' diff --git a/src/main/java/com/example/demo/config/auth/CustomUserDetailService.java b/src/main/java/com/example/demo/config/auth/CustomUserDetailService.java index d9ece3ed..38ea71ed 100644 --- a/src/main/java/com/example/demo/config/auth/CustomUserDetailService.java +++ b/src/main/java/com/example/demo/config/auth/CustomUserDetailService.java @@ -10,21 +10,21 @@ import java.util.Optional; -@RequiredArgsConstructor @Service +@RequiredArgsConstructor public class CustomUserDetailService implements UserDetailsService { private final UserJPARepository accountJPARepository; @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - Optional optionalAccount = accountJPARepository.findByEmail(email); + public CustomUserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + Optional optionalUser = accountJPARepository.findByEmail(email); - if (optionalAccount.isEmpty()) { + if (optionalUser.isEmpty()) { return null; } else { - User account = optionalAccount.get(); - return new CustomUserDetails(account); + User user = optionalUser.get(); + return new CustomUserDetails(user); } } } diff --git a/src/main/java/com/example/demo/config/auth/CustomUserDetails.java b/src/main/java/com/example/demo/config/auth/CustomUserDetails.java index 948b94be..e39df458 100644 --- a/src/main/java/com/example/demo/config/auth/CustomUserDetails.java +++ b/src/main/java/com/example/demo/config/auth/CustomUserDetails.java @@ -7,18 +7,22 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.stream.Collectors; -@RequiredArgsConstructor @Getter +@RequiredArgsConstructor public class CustomUserDetails implements UserDetails { private final User user; @Override public Collection getAuthorities() { - return Arrays.stream(user.getEmail().split(",")).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); + List authorities = new ArrayList(); + authorities.add(new SimpleGrantedAuthority(user.getRole().toString())); + return authorities; } @Override diff --git a/src/main/java/com/example/demo/config/errors/GlobalExceptionHandler.java b/src/main/java/com/example/demo/config/errors/GlobalExceptionHandler.java index 7f65c1fd..1c8e5832 100644 --- a/src/main/java/com/example/demo/config/errors/GlobalExceptionHandler.java +++ b/src/main/java/com/example/demo/config/errors/GlobalExceptionHandler.java @@ -1,11 +1,9 @@ package com.example.demo.config.errors; import com.example.demo.config.errors.exception.*; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -38,6 +36,6 @@ public ResponseEntity serverError(Exception500 exception) { @ExceptionHandler(Exception.class) public ResponseEntity unknownError(Exception exception) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiUtils.error(exception.getMessage())); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponseBuilder.error(exception.getMessage())); } } diff --git a/src/main/java/com/example/demo/config/errors/exception/Exception400.java b/src/main/java/com/example/demo/config/errors/exception/Exception400.java index e5320e0c..fa170cc1 100644 --- a/src/main/java/com/example/demo/config/errors/exception/Exception400.java +++ b/src/main/java/com/example/demo/config/errors/exception/Exception400.java @@ -1,11 +1,8 @@ package com.example.demo.config.errors.exception; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import lombok.Getter; -import org.springframework.http.HttpStatus; -import org.springframework.validation.BindingResult; -import java.util.HashMap; import java.util.Map; @Getter @@ -22,8 +19,8 @@ public Exception400(String message) { errors = null; } - public ApiUtils.ApiResponse body(){ - return ApiUtils.fail(errors, getMessage()); + public ApiResponseBuilder.ApiResponse body(){ + return ApiResponseBuilder.fail(errors, getMessage()); } } diff --git a/src/main/java/com/example/demo/config/errors/exception/Exception401.java b/src/main/java/com/example/demo/config/errors/exception/Exception401.java index 338b6677..782a7695 100644 --- a/src/main/java/com/example/demo/config/errors/exception/Exception401.java +++ b/src/main/java/com/example/demo/config/errors/exception/Exception401.java @@ -1,8 +1,7 @@ package com.example.demo.config.errors.exception; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import lombok.Getter; -import org.springframework.http.HttpStatus; @Getter public class Exception401 extends RuntimeException { @@ -10,7 +9,7 @@ public Exception401(String message) { super(message); } - public ApiUtils.ApiResponse body(){ - return ApiUtils.error(getMessage()); + public ApiResponseBuilder.ApiResponse body(){ + return ApiResponseBuilder.error(getMessage()); } } diff --git a/src/main/java/com/example/demo/config/errors/exception/Exception403.java b/src/main/java/com/example/demo/config/errors/exception/Exception403.java index 5ab0632a..497b0c73 100644 --- a/src/main/java/com/example/demo/config/errors/exception/Exception403.java +++ b/src/main/java/com/example/demo/config/errors/exception/Exception403.java @@ -1,8 +1,7 @@ package com.example.demo.config.errors.exception; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import lombok.Getter; -import org.springframework.http.HttpStatus; @Getter public class Exception403 extends RuntimeException { @@ -10,7 +9,7 @@ public Exception403(String message) { super(message); } - public ApiUtils.ApiResponse body(){ - return ApiUtils.error(getMessage()); + public ApiResponseBuilder.ApiResponse body(){ + return ApiResponseBuilder.error(getMessage()); } } \ No newline at end of file diff --git a/src/main/java/com/example/demo/config/errors/exception/Exception404.java b/src/main/java/com/example/demo/config/errors/exception/Exception404.java index c1057376..e97d66a4 100644 --- a/src/main/java/com/example/demo/config/errors/exception/Exception404.java +++ b/src/main/java/com/example/demo/config/errors/exception/Exception404.java @@ -1,14 +1,13 @@ package com.example.demo.config.errors.exception; -import com.example.demo.config.utils.ApiUtils; -import org.springframework.http.HttpStatus; +import com.example.demo.config.utils.ApiResponseBuilder; public class Exception404 extends RuntimeException { public Exception404(String message) { super(message); } - public ApiUtils.ApiResponse body(){ - return ApiUtils.error(getMessage()); + public ApiResponseBuilder.ApiResponse body(){ + return ApiResponseBuilder.error(getMessage()); } } diff --git a/src/main/java/com/example/demo/config/errors/exception/Exception500.java b/src/main/java/com/example/demo/config/errors/exception/Exception500.java index b42e1326..97a2976a 100644 --- a/src/main/java/com/example/demo/config/errors/exception/Exception500.java +++ b/src/main/java/com/example/demo/config/errors/exception/Exception500.java @@ -1,8 +1,7 @@ package com.example.demo.config.errors.exception; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import lombok.Getter; -import org.springframework.http.HttpStatus; @Getter public class Exception500 extends RuntimeException { @@ -10,7 +9,7 @@ public Exception500(String message) { super(message); } - public ApiUtils.ApiResponse body() { - return ApiUtils.error(getMessage()); + public ApiResponseBuilder.ApiResponse body() { + return ApiResponseBuilder.error(getMessage()); } } diff --git a/src/main/java/com/example/demo/config/jwt/JWTAuthenticationFilter.java b/src/main/java/com/example/demo/config/jwt/JWTAuthenticationFilter.java index 995b57af..2246ecf9 100644 --- a/src/main/java/com/example/demo/config/jwt/JWTAuthenticationFilter.java +++ b/src/main/java/com/example/demo/config/jwt/JWTAuthenticationFilter.java @@ -1,11 +1,10 @@ package com.example.demo.config.jwt; -import com.auth0.jwt.exceptions.SignatureVerificationException; -import com.auth0.jwt.exceptions.TokenExpiredException; -import com.auth0.jwt.interfaces.DecodedJWT; import com.example.demo.user.User; import com.example.demo.config.auth.CustomUserDetails; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -17,42 +16,35 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; -@Slf4j public class JWTAuthenticationFilter extends BasicAuthenticationFilter { - public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { + + private final String Authorization = "Authorization"; + + private final JWTTokenProvider jwtTokenProvider; + + public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTTokenProvider jwtTokenProvider) { super(authenticationManager); + this.jwtTokenProvider = jwtTokenProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - String jwt = request.getHeader(JWTTokenProvider.HEADER); + String jwtAccessToken = request.getHeader(Authorization); - if (jwt == null) { + if (jwtAccessToken == null || !(jwtAccessToken.startsWith("Bearer "))) { chain.doFilter(request, response); return; } - try { - DecodedJWT decodedJWT = JWTTokenProvider.verify(jwt); - int id = decodedJWT.getClaim("user_id").asInt(); - String email = decodedJWT.getClaim("user_email").asString(); - User user = User.builder().email(email).build(); - CustomUserDetails customUserDetails = new CustomUserDetails(user); - Authentication authentication = - new UsernamePasswordAuthenticationToken( - customUserDetails, - customUserDetails.getPassword(), - customUserDetails.getAuthorities() - ); + String extractedJwtAccessToken = jwtAccessToken.replace(JWTTokenProvider.Token_Prefix, ""); + if (jwtTokenProvider.validateToken(extractedJwtAccessToken)) { + Authentication authentication = jwtTokenProvider.getAuthentication(extractedJwtAccessToken); SecurityContextHolder.getContext().setAuthentication(authentication); - System.out.println("디버그 : 인증 객체 만들어짐"); - } catch (SignatureVerificationException sve) { - System.out.println("토큰 검증 실패"); - } catch (TokenExpiredException tee) { - System.out.println("토큰 만료됨"); - } finally { - chain.doFilter(request, response); } + chain.doFilter(request, response); } } diff --git a/src/main/java/com/example/demo/config/jwt/JWTTokenProvider.java b/src/main/java/com/example/demo/config/jwt/JWTTokenProvider.java index 28dae609..215c0eb5 100644 --- a/src/main/java/com/example/demo/config/jwt/JWTTokenProvider.java +++ b/src/main/java/com/example/demo/config/jwt/JWTTokenProvider.java @@ -1,36 +1,128 @@ package com.example.demo.config.jwt; -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.exceptions.SignatureVerificationException; -import com.auth0.jwt.exceptions.TokenExpiredException; -import com.auth0.jwt.interfaces.DecodedJWT; +import com.example.demo.config.auth.CustomUserDetailService; +import com.example.demo.config.auth.CustomUserDetails; +import com.example.demo.config.errors.exception.Exception401; import com.example.demo.user.User; +import com.example.demo.refreshToken.RefreshToken; +import com.example.demo.refreshToken.RefreshTokenJPARepository; +import com.example.demo.refreshToken.TokenResponse; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.SignatureException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import java.util.Date; +import java.util.Optional; +@RequiredArgsConstructor @Component public class JWTTokenProvider { - public static final String SECRET = "SecretKey"; - public static final Long EXP = 1000L * 60 * 30; // 30분 - public static final String HEADER = "Authorization"; - public static final String TOKEN_PREFIX = "Bearer "; // 스페이스 필요함 - - public static String create(User account) { - String jwt = JWT.create() - .withSubject(account.getEmail()) - .withExpiresAt(new Date(System.currentTimeMillis() + EXP)) - .withClaim("user_id", account.getId()) - .withClaim("user_email", account.getEmail()) - .sign(Algorithm.HMAC512(SECRET)); - return TOKEN_PREFIX + jwt; + + public static final String Header = "Authorization"; + public static final String Token_Prefix = "Bearer "; + + public static final String SecretKey = "a2FrYW8tdGVjaC1jYW1wdXMtcHJvamVjdC1nYXJkZW4tc3ByaW5nLXNlY3VyaXR5LWp3dC10b2tlbi1zZWNyZXQta2V5"; + public static final int AccessTokenValidTime = 1000 * 60 * 2; + public static final int RefreshTokenValidTime = 1000 * 60 * 5; + + private final CustomUserDetailService userDetailService; + private final RefreshTokenJPARepository refreshTokenJPARepository; + + public static TokenResponse.TokenDTO createToken(User user) { + String accessToken = createAccessToken(user); + String refreshToken = createRefreshToken(user); + return new TokenResponse.TokenDTO(accessToken, refreshToken); } - public static DecodedJWT verify(String jwt) throws SignatureVerificationException, TokenExpiredException { - jwt = jwt.replace(JWTTokenProvider.TOKEN_PREFIX, ""); - DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512(SECRET)) - .build().verify(jwt); - return decodedJWT; + public static String createAccessToken(User user) { + String accessToken = Jwts.builder() + .setSubject(user.getEmail()) + .claim("id", user.getId()) + .claim("firstName", user.getFirstName()) + .claim("lastName", user.getLastName()) + .claim("email", user.getEmail()) + .claim("country", user.getCountry()) + .claim("introduction", user.getIntroduction()) + .claim("age", user.getAge()) + .claim("phone", user.getPhone()) + .claim("profileImage", user.getProfileImage()) + .claim("role", user.getRole()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + AccessTokenValidTime)) + .signWith(SignatureAlgorithm.HS512, SecretKey) + .compact(); + return accessToken; } + + public static String createRefreshToken(User user) { + String refreshToken = Jwts.builder() + .setSubject(user.getEmail()) + .claim("id", user.getId()) + .claim("email", user.getEmail()) + .claim("role", user.getRole()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + RefreshTokenValidTime)) + .signWith(SignatureAlgorithm.HS512, SecretKey) + .compact(); + return refreshToken; + } + + public Claims decodeJwtToken(String token) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(SecretKey) + .build() + .parseClaimsJws(token) + .getBody(); + return claims; + } + + // JWT 토큰 복호화 -> 인증 정보 조회 + public Authentication getAuthentication(String accessToken) { + Claims claims = decodeJwtToken(accessToken); + CustomUserDetails userDetails = userDetailService.loadUserByUsername(claims.getSubject()); + return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); + } + + public static boolean validateToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(SecretKey) + .build() + .parseClaimsJws(token); + return true; + } catch (SignatureException e) { + System.out.println("Invalid JWT Signature."); + } catch (MalformedJwtException e) { + System.out.println("Invalid JWT Token."); + } catch (ExpiredJwtException e) { + System.out.println("Expired JWT Token."); + } catch (UnsupportedJwtException e) { + System.out.println("Unsupported JWT Token."); + } catch (IllegalArgumentException e) { + System.out.println("JWT claims string is empty."); + } + return false; + } + +// public static boolean validateRefreshToken(String token) { +// try { +// Jwts.parserBuilder() +// .setSigningKey(SecretKey) +// .build() +// .parseClaimsJws(token); +// return true; +// } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { +// System.out.println("Invalid JWT Token"); +// } catch (ExpiredJwtException e) { +// System.out.println("Expired JWT Token"); +// } catch (UnsupportedJwtException e) { +// System.out.println("Unsupported JWT Token"); +// } catch (IllegalArgumentException e) { +// System.out.println("JWT claims string is empty."); +// } +// return false; +// } } diff --git a/src/main/java/com/example/demo/config/security/SecurityConfig.java b/src/main/java/com/example/demo/config/security/SecurityConfig.java index 71f54b67..72bcc609 100644 --- a/src/main/java/com/example/demo/config/security/SecurityConfig.java +++ b/src/main/java/com/example/demo/config/security/SecurityConfig.java @@ -3,11 +3,15 @@ import com.example.demo.config.errors.exception.Exception401; import com.example.demo.config.errors.exception.Exception403; import com.example.demo.config.jwt.JWTAuthenticationFilter; +import com.example.demo.config.jwt.JWTTokenProvider; import com.example.demo.config.utils.FilterResponseUtils; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ScopeMetadataResolver; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.factory.PasswordEncoderFactories; @@ -18,7 +22,11 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration +@RequiredArgsConstructor public class SecurityConfig { + + private final JWTTokenProvider jwtTokenProvider; + @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); @@ -28,15 +36,15 @@ public class CustomSecurityFilterManager extends AbstractHttpConfigurer Token 방식 httpSecurity.httpBasic().disable(); // 7. 커스텀 필터 적용 (시큐리티 필터 교환) @@ -58,18 +66,18 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws // 8. 인증 실패 처리 httpSecurity.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { - FilterResponseUtils.unAuthorized(response, new Exception401("인증되지 않았습니다")); + FilterResponseUtils.unAuthorized(response, new Exception401("인증되지 않은 사용자입니다.")); }); // 9. 권한 실패 처리 httpSecurity.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> { - FilterResponseUtils.forbidden(response, new Exception403("권한이 없습니다")); + FilterResponseUtils.forbidden(response, new Exception403("권한이 없는 사용자입니다.")); }); // 11. 인증, 권한 필터 설정 httpSecurity.authorizeRequests( - authorize -> authorize.antMatchers("/albums/**", "/photos/**").authenticated() - .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") + authorize -> authorize.antMatchers("/profiles/**").authenticated() + .antMatchers("/admin/**").access("hasRole('ADMIN')") .anyRequest().permitAll() ); @@ -80,9 +88,9 @@ public CorsConfigurationSource configurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.addAllowedHeader("*"); configuration.addAllowedMethod("*"); // GET, POST, PUT, DELETE (Javascript 요청 허용) - configuration.addAllowedOriginPattern("*"); // 모든 IP 주소 허용 (프론트 앤드 IP만 허용 react) + configuration.addAllowedOriginPattern("http://localhost:8080"); // 모든 IP 주소 허용 (프론트 앤드 IP만 허용 react) configuration.setAllowCredentials(true); // 클라이언트에서 쿠키 요청 허용 - configuration.addExposedHeader("Authorization"); // 옛날에는 디폴트 였다. 지금은 아닙니다. + configuration.addExposedHeader("Authorization"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); diff --git a/src/main/java/com/example/demo/config/utils/ApiUtils.java b/src/main/java/com/example/demo/config/utils/ApiResponseBuilder.java similarity index 79% rename from src/main/java/com/example/demo/config/utils/ApiUtils.java rename to src/main/java/com/example/demo/config/utils/ApiResponseBuilder.java index 78bc3f95..f21be826 100644 --- a/src/main/java/com/example/demo/config/utils/ApiUtils.java +++ b/src/main/java/com/example/demo/config/utils/ApiResponseBuilder.java @@ -3,16 +3,10 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import org.springframework.http.HttpStatus; -import org.springframework.validation.BindingResult; -import org.springframework.validation.FieldError; -import org.springframework.validation.ObjectError; -import java.util.HashMap; -import java.util.List; import java.util.Map; -public class ApiUtils { +public class ApiResponseBuilder { private static final String SUCCESS_STATUS = "success"; private static final String FAIL_STATUS = "fail"; diff --git a/src/main/java/com/example/demo/config/utils/StateConverter.java b/src/main/java/com/example/demo/config/utils/StateConverter.java deleted file mode 100644 index e109d5bf..00000000 --- a/src/main/java/com/example/demo/config/utils/StateConverter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.demo.config.utils; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -import javax.persistence.AttributeConverter; -import java.util.Optional; - -@RequiredArgsConstructor -@Component -public class StateConverter implements AttributeConverter { - - @Override - public String convertToDatabaseColumn(StateEnum attribute) { - return Optional.ofNullable(attribute).orElse(StateEnum.NULL).getValue(); - } - - @Override - public StateEnum convertToEntityAttribute(String dbData) { - if (dbData == null || dbData.length() == 0) { - return StateEnum.NULL; - } - return StateEnum.findOf(dbData); - } -} diff --git a/src/main/java/com/example/demo/config/utils/StateEnum.java b/src/main/java/com/example/demo/config/utils/StateEnum.java deleted file mode 100644 index af51d0c3..00000000 --- a/src/main/java/com/example/demo/config/utils/StateEnum.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.demo.config.utils; - -import lombok.Getter; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@Getter -public enum StateEnum { - NULL("NULL", ""), - ACTIVE("ACTIVE", "ACTIVE"), - DONE("DONE", "DONE"), - DELETED("DELETED", "DELETED"); - - private final String desc; - private final String value; - - StateEnum(String desc, String value) { - this.desc = desc; - this.value = value; - } - - private static final Map descriptions = Collections.unmodifiableMap(Stream.of(values()).collect(Collectors.toMap(StateEnum::getValue, Function.identity()))); - - public static StateEnum findOf(String findValue) { - return Optional.ofNullable(descriptions.get(findValue)).orElse(NULL); - } -} diff --git a/src/main/java/com/example/demo/interest/Interest.java b/src/main/java/com/example/demo/interest/Interest.java index 84dbb3f1..504be61f 100644 --- a/src/main/java/com/example/demo/interest/Interest.java +++ b/src/main/java/com/example/demo/interest/Interest.java @@ -11,7 +11,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -@Table(name = "interest_tb") +@Table(name = "interests") public class Interest extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/example/demo/mentoring/MentorPost.java b/src/main/java/com/example/demo/mentoring/MentorPost.java index 0a53a704..1d543f24 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPost.java +++ b/src/main/java/com/example/demo/mentoring/MentorPost.java @@ -1,14 +1,11 @@ package com.example.demo.mentoring; import com.example.demo.config.utils.BaseTime; -import com.example.demo.config.utils.StateConverter; -import com.example.demo.config.utils.StateEnum; import com.example.demo.user.User; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; @@ -34,9 +31,9 @@ public class MentorPost extends BaseTime { @Column(length = 300) private String content; - @Convert(converter = StateConverter.class) + @Convert(converter = MentorPostStateConverter.class) @Column(name = "state", nullable = false) - private StateEnum state = StateEnum.ACTIVE; + private MentorPostStateEnum state = MentorPostStateEnum.ACTIVE; @Builder public MentorPost(User writer, String title, String content){ @@ -51,8 +48,8 @@ public void update(String title, String content) this.content = content; } - public void changeStatus(StateEnum stateEnum) + public void changeStatus(MentorPostStateEnum mentorPostStateEnum) { - this.state = stateEnum; + this.state = mentorPostStateEnum; } } diff --git a/src/main/java/com/example/demo/mentoring/MentorPostCategoryEnum.java b/src/main/java/com/example/demo/mentoring/MentorPostCategoryEnum.java new file mode 100644 index 00000000..0e4829eb --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/MentorPostCategoryEnum.java @@ -0,0 +1,5 @@ +package com.example.demo.mentoring; + +public enum MentorPostCategoryEnum { + NULL, TITLE, WRITER, INTEREST; +} diff --git a/src/main/java/com/example/demo/mentoring/MentorPostJPARepostiory.java b/src/main/java/com/example/demo/mentoring/MentorPostJPARepostiory.java index bf6e112b..6da3fd3b 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostJPARepostiory.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostJPARepostiory.java @@ -1,9 +1,11 @@ package com.example.demo.mentoring; +import org.springframework.data.domain.Page; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.data.domain.Pageable; import java.util.List; import java.util.Optional; @@ -13,6 +15,18 @@ public interface MentorPostJPARepostiory extends JpaRepository findAllByWriter(@Param("writer") int writer); + @Query("select m from MentorPost m where m.writer.id = :writer and m.state = 'DONE'") + List findAllByWriterDone(@Param("writer") int writer); + + @Query("select m from MentorPost m where m.title like :keyword") + Page findAllByTitleKeyword(@Param("keyword") String keyword, Pageable pageable); + + @Query("select m from MentorPost m where m.writer.firstName like :keyword or m.writer.firstName like :keyword") + Page findAllByWriterKeyword(@Param("keyword") String keyword, Pageable pageable); + + @Query("select m from MentorPost m inner join UserInterest ui ON m.writer.id = ui.user.id where ui.interest.category like :keyword") + Page findAllByInterestKeyword(@Param("keyword") String keyword, Pageable pageable); + Optional findById(int id); @Query("select count(*) from MentorPost m where m.writer.id = :userId and m.state = 'ACTIVE'") @@ -21,6 +35,4 @@ public interface MentorPostJPARepostiory extends JpaRepository findAllByMenteeUserId(@Param("menteeUserId") int menteeUserId); } diff --git a/src/main/java/com/example/demo/mentoring/MentorPostRequest.java b/src/main/java/com/example/demo/mentoring/MentorPostRequest.java index a32c24f9..a2fd23c9 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostRequest.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostRequest.java @@ -1,7 +1,5 @@ package com.example.demo.mentoring; -import com.example.demo.config.utils.StateEnum; -import com.example.demo.user.User; import lombok.Getter; import lombok.Setter; import javax.validation.constraints.NotNull; @@ -20,6 +18,6 @@ public static class CreateDTO { @Getter @Setter public static class StateDTO { - private StateEnum stateEnum; + private MentorPostStateEnum mentorPostStateEnum; } } diff --git a/src/main/java/com/example/demo/mentoring/MentorPostResponse.java b/src/main/java/com/example/demo/mentoring/MentorPostResponse.java index 84a9d647..405a019b 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostResponse.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostResponse.java @@ -1,7 +1,7 @@ package com.example.demo.mentoring; -import com.example.demo.config.utils.StateEnum; +import com.example.demo.mentoring.contact.ContactStateEnum; import com.example.demo.mentoring.contact.NotConnectedRegisterUser; import com.example.demo.user.Role; import com.example.demo.user.User; @@ -27,14 +27,14 @@ public static class MentorPostAllDTO { private int postId; private String title; private String content; - private StateEnum stateEnum; + private MentorPostStateEnum mentorPostStateEnum; private WriterDTO writerDTO; public MentorPostAllDTO(MentorPost mentorPost, List userInterests) { this.postId = mentorPost.getId(); this.title = mentorPost.getTitle(); this.content = mentorPost.getContent(); - this.stateEnum = mentorPost.getState(); + this.mentorPostStateEnum = mentorPost.getState(); WriterDTO writerDTO = new MentorPostAllDTO.WriterDTO(mentorPost.getWriter(), userInterests); this.writerDTO = writerDTO; } @@ -79,14 +79,14 @@ public static class MentorPostDTO { private String title; private String content; private WriterDTO writerDTO; - private StateEnum stateEnum; + private MentorPostStateEnum mentorPostStateEnum; private List menteeDTOList; public MentorPostDTO(MentorPost mentorPost, List mentorFavorites, List mentees, List menteeInterest) { this.postId = mentorPost.getId(); this.title = mentorPost.getTitle(); this.content = mentorPost.getContent(); - this.stateEnum = mentorPost.getState(); + this.mentorPostStateEnum = mentorPost.getState(); MentorPostDTO.WriterDTO writerDTO = new MentorPostDTO.WriterDTO(mentorPost.getWriter(), mentorFavorites); this.writerDTO = writerDTO; List menteeDTOList = mentees.stream() @@ -95,7 +95,7 @@ public MentorPostDTO(MentorPost mentorPost, List mentorFavorites, userInterest -> mentee.getMenteeUser().getId() == userInterest.getUser().getId() ).collect(Collectors.toList()); - MentorPostDTO.MenteeDTO menteeDTO = new MentorPostDTO.MenteeDTO(mentee.getMenteeUser(), eachMenteeFavorite); + MentorPostDTO.MenteeDTO menteeDTO = new MentorPostDTO.MenteeDTO(mentee, eachMenteeFavorite); return menteeDTO; }) .collect(Collectors.toList()); @@ -131,15 +131,17 @@ public static class MenteeDTO{ private String country; private Role role; private int age; + private ContactStateEnum state; private List interests; - public MenteeDTO(User user, List userInterests) { - this.menteeId = user.getId(); - this.profileImage = user.getProfileImage(); - this.name = user.getFirstName() + " " + user.getLastName(); - this.country = user.getCountry(); - this.role = user.getRole(); - this.age = user.getAge(); + public MenteeDTO(NotConnectedRegisterUser notConnectedRegisterUser, List userInterests) { + this.menteeId = notConnectedRegisterUser.getId(); + this.profileImage = notConnectedRegisterUser.getMenteeUser().getProfileImage(); + this.name = notConnectedRegisterUser.getMenteeUser().getFirstName() + " " + notConnectedRegisterUser.getMenteeUser().getLastName(); + this.country = notConnectedRegisterUser.getMenteeUser().getCountry(); + this.role = notConnectedRegisterUser.getMenteeUser().getRole(); + this.age = notConnectedRegisterUser.getMenteeUser().getAge(); + this.state = notConnectedRegisterUser.getState(); this.interests = userInterests.stream() .map(userInterest -> userInterest.getInterest().getCategory()) .collect(Collectors.toList()); @@ -153,7 +155,7 @@ public static class MentorPostAllWithTimeStampDTO { private int postId; private String title; private String content; - private StateEnum stateEnum; + private MentorPostStateEnum mentorPostStateEnum; private WriterDTO writerDTO; private LocalDateTime createdAt; private LocalDateTime deletedAt; @@ -163,7 +165,7 @@ public MentorPostAllWithTimeStampDTO(MentorPost mentorPost, List u this.postId = mentorPost.getId(); this.title = mentorPost.getTitle(); this.content = mentorPost.getContent(); - this.stateEnum = mentorPost.getState(); + this.mentorPostStateEnum = mentorPost.getState(); WriterDTO writerDTO = new MentorPostAllWithTimeStampDTO.WriterDTO(mentorPost.getWriter(), userInterests); this.writerDTO = writerDTO; this.createdAt = mentorPost.getCreatedAt(); diff --git a/src/main/java/com/example/demo/mentoring/MentorPostRestController.java b/src/main/java/com/example/demo/mentoring/MentorPostRestController.java index baa88154..17fd6dc2 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostRestController.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostRestController.java @@ -1,7 +1,7 @@ package com.example.demo.mentoring; import com.example.demo.config.auth.CustomUserDetails; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -20,37 +20,44 @@ public class MentorPostRestController { @PostMapping(value = "/mentorings/post") public ResponseEntity createMentorPost(@RequestBody @Valid MentorPostRequest.CreateDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { mentorPostService.createMentorPost(requestDTO, userDetails.getUser()); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.successWithNoContent()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } @GetMapping("/mentorings/post") - public ResponseEntity getMentorPost(@RequestParam(value = "page", defaultValue = "0") Integer page, @AuthenticationPrincipal CustomUserDetails userDetails) { - List responseDTOs = mentorPostService.findAllMentorPost(page); - return ResponseEntity.ok(ApiUtils.success(responseDTOs)); + public ResponseEntity getMentorPost( + @RequestParam(value = "category", required = false) MentorPostCategoryEnum category, + @RequestParam(value = "search", defaultValue = "") String keyword, + @RequestParam(value = "page", defaultValue = "0") Integer page) { + + if(category == null) + category = MentorPostCategoryEnum.NULL; + + List responseDTOs = mentorPostService.findAllMentorPost(category, keyword, page); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTOs)); } @GetMapping("/mentorings/post/{id}") - public ResponseEntity getMentorPostId(@PathVariable int id, @AuthenticationPrincipal CustomUserDetails userDetails) { + public ResponseEntity getMentorPostId(@PathVariable int id) { MentorPostResponse.MentorPostDTO responseDTO = mentorPostService.findMentorPost(id); - return ResponseEntity.ok(ApiUtils.success(responseDTO)); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } @PutMapping(value = "/mentorings/post/{id}") public ResponseEntity updateMentorPost(@PathVariable int id, @RequestBody @Valid MentorPostRequest.CreateDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { - mentorPostService.updateMentorPost(requestDTO, id); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.successWithNoContent()); + mentorPostService.updateMentorPost(requestDTO, id, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } @DeleteMapping(value = "/mentorings/post/{id}") - public ResponseEntity deleteMentorPost(@PathVariable int id, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { - mentorPostService.deleteMentorPost(id); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.successWithNoContent()); + public ResponseEntity deleteMentorPost(@PathVariable int id, @AuthenticationPrincipal CustomUserDetails userDetails) { + mentorPostService.deleteMentorPost(id, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } @PatchMapping(value = "/mentorings/post/{id}/done") public ResponseEntity changeMentorPostStatus(@PathVariable int id,@RequestBody @Valid MentorPostRequest.StateDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { - mentorPostService.changeMentorPostStatus(requestDTO, id); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.successWithNoContent()); + mentorPostService.changeMentorPostStatus(requestDTO, id, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } } diff --git a/src/main/java/com/example/demo/mentoring/MentorPostService.java b/src/main/java/com/example/demo/mentoring/MentorPostService.java index eabed99c..be3f9c45 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostService.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostService.java @@ -1,10 +1,11 @@ package com.example.demo.mentoring; +import com.example.demo.config.errors.exception.Exception401; import com.example.demo.config.errors.exception.Exception500; -import com.example.demo.config.errors.exception.Exception400; import com.example.demo.config.errors.exception.Exception404; import com.example.demo.mentoring.contact.ContactJPARepository; import com.example.demo.mentoring.contact.NotConnectedRegisterUser; +import com.example.demo.user.Role; import com.example.demo.user.User; import com.example.demo.user.userInterest.UserInterest; import com.example.demo.user.userInterest.UserInterestJPARepository; @@ -14,11 +15,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.example.demo.config.errors.exception.Exception400; -import com.example.demo.config.errors.exception.Exception404; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @Transactional @@ -32,7 +30,17 @@ public class MentorPostService { //mentorPost생성 @Transactional public void createMentorPost(MentorPostRequest.CreateDTO createDTO, User writer) { + if ( writer.getRole() == Role.MENTEE ) { + throw new Exception401("해당 사용자는 멘티입니다."); + } + + //글자수 확인 + if(createDTO.getContent().length() > 300){ + throw new Exception404("글자수가 300자를 넘어갑니다."); + } + MentorPost mentorPost = new MentorPost( writer, createDTO.getTitle(), createDTO.getContent()); + try { mentorPostJPARepository.save(mentorPost); } catch (Exception e) { @@ -43,10 +51,32 @@ public void createMentorPost(MentorPostRequest.CreateDTO createDTO, User writer) /* 1. mentorPostList를 조회 2. 각 List당 writer별 writerInterests를 조회 3. MentorPostDTO 생성*/ - public List findAllMentorPost(int page) { + public List findAllMentorPost(MentorPostCategoryEnum searchCategory, String keyword, int page) { Pageable pageable = PageRequest.of(page,5); + Page pageContent = null; + + //검색별 pageContent 검색 + if(searchCategory == MentorPostCategoryEnum.NULL) + { + pageContent = mentorPostJPARepository.findAll(pageable); + } + else if(searchCategory == MentorPostCategoryEnum.TITLE) + { + pageContent = mentorPostJPARepository.findAllByTitleKeyword("%" + keyword + "%", pageable); + } + else if(searchCategory == MentorPostCategoryEnum.WRITER) + { + pageContent = mentorPostJPARepository.findAllByWriterKeyword("%" + keyword + "%", pageable); + } + else if(searchCategory == MentorPostCategoryEnum.INTEREST) + { + pageContent = mentorPostJPARepository.findAllByInterestKeyword("%" + keyword + "%", pageable); + } + else + { + throw new Exception404("검색 분류가 잘못되었습니다."); + } - Page pageContent = mentorPostJPARepository.findAll(pageable); if(pageContent.getTotalPages() == 0){ throw new Exception404("해당 글들이 존재하지 않습니다"); @@ -56,10 +86,6 @@ public List findAllMentorPost(int page) { List mentorPostDTOList = pageContent.getContent().stream().map( mentorPost -> { List writerInterests = userInterestJPARepository.findAllById(mentorPost.getWriter().getId()); - if(writerInterests.isEmpty()){ - throw new Exception404("해당 카테고리는 존재하지 않습니다"); - } - return new MentorPostResponse.MentorPostAllDTO(mentorPost,writerInterests); } ).collect(Collectors.toList()); @@ -71,6 +97,7 @@ public MentorPostResponse.MentorPostDTO findMentorPost(int id){ .orElseThrow(() -> new Exception404("해당 글이 존재하지 않습니다.\n" + "id : " + id)); //writer 데이터 + //writer를 제외하고는 다 null 가능 User mentor = mentorPost.getWriter(); //mentee들 데이터 List menteeList = contactJPARepository.findAllByMentorPostId(id); @@ -86,11 +113,17 @@ public MentorPostResponse.MentorPostDTO findMentorPost(int id){ return mentorPostDTO; } @Transactional - public void updateMentorPost(MentorPostRequest.CreateDTO createDTO, int id) - { + public void updateMentorPost(MentorPostRequest.CreateDTO createDTO, int id, User writer) { + isMentor(writer); + MentorPost mentorPost = mentorPostJPARepository.findById(id). orElseThrow(() -> new Exception404("해당 글이 존재하지 않습니다.")); + //글자수 확인 + if(createDTO.getContent().length() > 300){ + throw new Exception404("글자수가 300자를 넘어갑니다."); + } + try { mentorPost.update(createDTO.getTitle(), createDTO.getContent()); } catch (Exception e) { @@ -98,7 +131,10 @@ public void updateMentorPost(MentorPostRequest.CreateDTO createDTO, int id) } } - public void deleteMentorPost(int id) { + public void deleteMentorPost(int id, User writer) { + + isMentor(writer); + try { mentorPostJPARepository.deleteById(id); } catch (Exception e) { @@ -123,11 +159,24 @@ public List findAllMentorPostW return mentorPostDTOList; } - public void changeMentorPostStatus(MentorPostRequest.StateDTO stateDTO, int id) - { + public void changeMentorPostStatus(MentorPostRequest.StateDTO stateDTO, int id, User writer) { + + isMentor(writer); + MentorPost mentorPost = mentorPostJPARepository.findById(id) .orElseThrow(() -> new Exception404("해당 글이 존재하지 않습니다."));; - mentorPost.changeStatus(stateDTO.getStateEnum()); + + try { + mentorPost.changeStatus(stateDTO.getMentorPostStateEnum()); + } catch (Exception e) { + throw new Exception500("unknown server error"); + } + } + + private void isMentor(User writer) { + if ( writer.getRole() == Role.MENTEE ) { + throw new Exception401("해당 사용자는 멘티입니다."); + } } } diff --git a/src/main/java/com/example/demo/mentoring/MentorPostStateConverter.java b/src/main/java/com/example/demo/mentoring/MentorPostStateConverter.java new file mode 100644 index 00000000..39c83ff8 --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/MentorPostStateConverter.java @@ -0,0 +1,22 @@ +package com.example.demo.mentoring; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.persistence.AttributeConverter; +import java.util.Optional; + +@RequiredArgsConstructor +@Component +public class MentorPostStateConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(MentorPostStateEnum attribute) { + return attribute.getValue(); + } + + @Override + public MentorPostStateEnum convertToEntityAttribute(String dbData) { + return MentorPostStateEnum.findOf(dbData); + } +} diff --git a/src/main/java/com/example/demo/mentoring/MentorPostStateEnum.java b/src/main/java/com/example/demo/mentoring/MentorPostStateEnum.java new file mode 100644 index 00000000..3795e105 --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/MentorPostStateEnum.java @@ -0,0 +1,30 @@ +package com.example.demo.mentoring; + +import lombok.Getter; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Getter +public enum MentorPostStateEnum { + ACTIVE("ACTIVE", "ACTIVE"), + DONE("DONE", "DONE"); + + private final String desc; + private final String value; + + MentorPostStateEnum(String desc, String value) { + this.desc = desc; + this.value = value; + } + + private static final Map descriptions = Collections.unmodifiableMap(Stream.of(values()).collect(Collectors.toMap(MentorPostStateEnum::getValue, Function.identity()))); + + public static MentorPostStateEnum findOf(String findValue) { + return descriptions.get(findValue); + } +} diff --git a/src/main/java/com/example/demo/mentoring/contact/ContactJPARepository.java b/src/main/java/com/example/demo/mentoring/contact/ContactJPARepository.java index 0758206b..a6a4d29c 100644 --- a/src/main/java/com/example/demo/mentoring/contact/ContactJPARepository.java +++ b/src/main/java/com/example/demo/mentoring/contact/ContactJPARepository.java @@ -1,9 +1,11 @@ package com.example.demo.mentoring.contact; +import com.example.demo.mentoring.MentorPost; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.Collection; import java.util.List; import java.util.Optional; @@ -14,4 +16,13 @@ public interface ContactJPARepository extends JpaRepository findByMentorPostIdAndMenteeUserId(int mentorPostId, int menteeId); + + @Query("SELECT count(*) FROM NotConnectedRegisterUser ncru WHERE ncru.menteeUser.id = :userId") + int countContactByMenteeId(int userId); + + @Query("SELECT ncru FROM NotConnectedRegisterUser ncru WHERE ncru.menteeUser.id = :menteeId") + List findAllByMenteeId(int menteeId); + + @Query("SELECT ncru FROM NotConnectedRegisterUser ncru WHERE ncru.mentorPost.writer.id = :mentorId") + List findAllByMentorId(int mentorId); } diff --git a/src/main/java/com/example/demo/mentoring/contact/ContactRequest.java b/src/main/java/com/example/demo/mentoring/contact/ContactRequest.java index 45522e35..51cb056c 100644 --- a/src/main/java/com/example/demo/mentoring/contact/ContactRequest.java +++ b/src/main/java/com/example/demo/mentoring/contact/ContactRequest.java @@ -1,6 +1,7 @@ package com.example.demo.mentoring.contact; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import javax.validation.constraints.NotNull; @@ -9,18 +10,37 @@ public class ContactRequest { @Getter @Setter - public static class AcceptDTO { + public static class ContactCreateDTO { private int mentorPostId; - private List mentorsAndMentees; - @Getter @Setter - public static class MentorAndMenteeDTO { - @NotNull - private int mentorId; + private int mentorId; + private int menteeId; + } + + @Getter @Setter + public static class ContactRefuseDTO { + private int mentorPostId; + private int mentorId; + private List mentees; + @Getter @Setter @NoArgsConstructor + public static class RefuseMenteeDTO { @NotNull private int menteeId; + public RefuseMenteeDTO(int menteeId) { + this.menteeId = menteeId; + } + } + } - public MentorAndMenteeDTO(int mentorId, int menteeId) { - this.mentorId = mentorId; + @Getter @Setter + public static class ContactAcceptDTO { + private int mentorPostId; + private int mentorId; + private List mentees; + @Getter @Setter @NoArgsConstructor + public static class AcceptMenteeDTO { + @NotNull + private int menteeId; + public AcceptMenteeDTO(int menteeId) { this.menteeId = menteeId; } } diff --git a/src/main/java/com/example/demo/mentoring/contact/ContactResponse.java b/src/main/java/com/example/demo/mentoring/contact/ContactResponse.java index 74923a0c..ff83058c 100644 --- a/src/main/java/com/example/demo/mentoring/contact/ContactResponse.java +++ b/src/main/java/com/example/demo/mentoring/contact/ContactResponse.java @@ -13,26 +13,28 @@ public class ContactResponse { @Getter @Setter - public static class postCountDTO { + public static class PostCountDTO { private int contactCount; private int doneCount; - public postCountDTO(int contactCount, int doneCount) { + public PostCountDTO(int contactCount, int doneCount) { this.contactCount = contactCount; this.doneCount = doneCount; } } @Getter @Setter - public static class MenteeContactDTO { + public static class ContactDashBoardMenteeDTO { private int postId; private String title; - private MentorDTO mentor; + private ContactMentorDTO mentor; + private int menteeId; - public MenteeContactDTO(MentorPost mentorPost, MentorDTO mentor) { + public ContactDashBoardMenteeDTO(MentorPost mentorPost, ContactMentorDTO mentor, NotConnectedRegisterUser notConnectedRegisterUser) { this.postId = mentorPost.getId(); this.title = mentorPost.getTitle(); this.mentor = mentor; + this.menteeId = notConnectedRegisterUser.getId(); } } /** @@ -45,13 +47,13 @@ public MenteeContactDTO(MentorPost mentorPost, MentorDTO mentor) { * * **/ @Getter @Setter - public static class MentorPostDTO { + public static class ContactDashboardMentorDTO { private int postId; private String title; - private MentorDTO mentor; - private List mentees; + private ContactMentorDTO mentor; + private List mentees; - public MentorPostDTO(MentorPost mentorPost, MentorDTO mentor, List mentees) { + public ContactDashboardMentorDTO(MentorPost mentorPost, ContactMentorDTO mentor, List mentees) { this.postId = mentorPost.getId(); this.title = mentorPost.getTitle(); this.mentor = mentor; @@ -59,59 +61,57 @@ public MentorPostDTO(MentorPost mentorPost, MentorDTO mentor, List me } } - @Getter @Setter - public static class MentorDTO { - private int mentorId; - private String profileImage; - private String name; - private String country; - private int age; - private Role role; - private List favorites; + @Getter @Setter + public static class ContactMentorDTO { + private int mentorId; + private String profileImage; + private String name; + private String country; + private int age; + private Role role; + private List favorites; - public MentorDTO(User user, List userInterests) { - this.mentorId = user.getId(); - this.profileImage = user.getProfileImage(); - this.name = user.getFirstName() + " " + user.getLastName(); - this.country = user.getCountry(); - this.age = user.getAge(); - this.role = user.getRole(); - this.favorites = userInterests.stream() - .filter(userInterest -> userInterest.getUser().getId() == user.getId()) - .map(userInterest -> userInterest.getInterest().getCategory()) - .collect(Collectors.toList()); - } + public ContactMentorDTO(User mentor, List userInterests) { + this.mentorId = mentor.getId(); + this.profileImage = mentor.getProfileImage(); + this.name = mentor.getFirstName() + " " + mentor.getLastName(); + this.country = mentor.getCountry(); + this.age = mentor.getAge(); + this.role = mentor.getRole(); + this.favorites = userInterests.stream() + .map(userInterest -> userInterest.getInterest().getCategory()) + .collect(Collectors.toList()); } - @Getter @Setter - public static class MenteeDTO { - private int menteeId; - private String profileImage; - private String name; - private String country; - private int age; - private Role role; - private NotConnectedRegisterUser.State state; - private List favorites; // 고민할 부분 : 유저의 favorite List 를 어떻게 가져올 것 인가? + } + @Getter @Setter + public static class ContactMenteeDTO { + private int menteeId; + private String profileImage; + private String name; + private String country; + private int age; + private Role role; + private ContactStateEnum state; + private List favorites; // 고민할 부분 : 유저의 favorite List 를 어떻게 가져올 것 인가? - /** - * 유저의 favorite List 를 가져오기 위해 - * userInterest 를 입력으로 받음 - * userInterest 의 userId 와 현재 신청한 멘티 ( notConnectedRegitserUser ) 의 id 값이 일치하는 경우 - * 그럴 경우에만 tag 값들을 가져오기 - * **/ + /** + * 유저의 favorite List 를 가져오기 위해 + * userInterest 를 입력으로 받음 + * userInterest 의 userId 와 현재 신청한 멘티 ( notConnectedRegitserUser ) 의 id 값이 일치하는 경우 + * 그럴 경우에만 tag 값들을 가져오기 + * **/ - public MenteeDTO(NotConnectedRegisterUser notConnectedRegisterUser, List userInterests) { - this.menteeId = notConnectedRegisterUser.getMenteeUser().getId(); - this.profileImage = notConnectedRegisterUser.getMenteeUser().getProfileImage(); - this.name = notConnectedRegisterUser.getMenteeUser().getFirstName() + " " + notConnectedRegisterUser.getMenteeUser().getLastName(); - this.country = notConnectedRegisterUser.getMenteeUser().getCountry(); - this.age = notConnectedRegisterUser.getMenteeUser().getAge(); - this.role = notConnectedRegisterUser.getMenteeUser().getRole(); - this.state = notConnectedRegisterUser.getState(); - this.favorites = userInterests.stream() - .filter(userInterest -> userInterest.getUser().getId() == notConnectedRegisterUser.getMenteeUser().getId()) - .map(userInterest -> userInterest.getInterest().getCategory()) - .collect(Collectors.toList()); - } + public ContactMenteeDTO(NotConnectedRegisterUser notConnectedRegisterUser, List userInterests) { + this.menteeId = notConnectedRegisterUser.getId(); + this.profileImage = notConnectedRegisterUser.getMenteeUser().getProfileImage(); + this.name = notConnectedRegisterUser.getMenteeUser().getFirstName() + " " + notConnectedRegisterUser.getMenteeUser().getLastName(); + this.country = notConnectedRegisterUser.getMenteeUser().getCountry(); + this.age = notConnectedRegisterUser.getMenteeUser().getAge(); + this.role = notConnectedRegisterUser.getMenteeUser().getRole(); + this.state = notConnectedRegisterUser.getState(); + this.favorites = userInterests.stream() + .map(userInterest -> userInterest.getInterest().getCategory()) + .collect(Collectors.toList()); } + } } diff --git a/src/main/java/com/example/demo/mentoring/contact/ContactRestController.java b/src/main/java/com/example/demo/mentoring/contact/ContactRestController.java index 353a41ba..e9544d03 100644 --- a/src/main/java/com/example/demo/mentoring/contact/ContactRestController.java +++ b/src/main/java/com/example/demo/mentoring/contact/ContactRestController.java @@ -1,7 +1,8 @@ package com.example.demo.mentoring.contact; import com.example.demo.config.auth.CustomUserDetails; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; +import com.example.demo.mentoring.done.DoneService; import com.example.demo.user.Role; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; @@ -21,13 +22,13 @@ public class ContactRestController { @GetMapping(value = "/contacts") @Operation(summary = "contact 화면 조회", description = "멘토, 멘티 화면에 따라 적절한 화면을 보여준다.") - public ResponseEntity findAll(@AuthenticationPrincipal CustomUserDetails userDetails) { + public ResponseEntity findAllContacts(@AuthenticationPrincipal CustomUserDetails userDetails) { if ( userDetails.getUser().getRole() == Role.MENTEE ) { - List responseDTO = contactService.findAllByMentee(userDetails.getUser().getId()); - return ResponseEntity.ok(ApiUtils.success(responseDTO)); + List responseDTO = contactService.findAllByMentee(userDetails.getUser()); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } - List responseDTO = contactService.findAllByMentor(userDetails.getUser().getId()); - return ResponseEntity.ok(ApiUtils.success(responseDTO)); + List responseDTO = contactService.findAllByMentor(userDetails.getUser()); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } @@ -40,21 +41,44 @@ public ResponseEntity postCounts(@AuthenticationPrincipal CustomUserDetails u Role role = userDetails.getUser().getRole(); if ( role == Role.MENTEE ) { - return null; + ContactResponse.PostCountDTO responseDTO = contactService.postCountsMyMentee(userDetails.getUser().getId()); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } - ContactResponse.postCountDTO responseDTO = contactService.postCountsByMentor(userDetails.getUser().getId()); - return ResponseEntity.ok(ApiUtils.success(responseDTO)); + ContactResponse.PostCountDTO responseDTO = contactService.postCountsByMentor(userDetails.getUser().getId()); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } - @PostMapping(value = "/contacts/{id}/accept") + @PostMapping(value = "/contacts/accept") @Operation(summary = "멘토링 신청 수락", description = "멘토링 신청을 수락한다.") - public ResponseEntity acceptContact(@PathVariable int id, @RequestBody @Valid ContactRequest.AcceptDTO acceptDTO, Error errors, @AuthenticationPrincipal CustomUserDetails userDetails) { + public ResponseEntity acceptContact(@RequestBody @Valid ContactRequest.ContactAcceptDTO contactAcceptDTO, Error errors, @AuthenticationPrincipal CustomUserDetails userDetails) { // TO-DO : 멘토링 신청 수락 API 로직 만들기 - contactService.acceptContact(id, acceptDTO, userDetails.getUser()); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.successWithNoContent()); + contactService.acceptContact(contactAcceptDTO, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } + @PatchMapping(value = "/contacts/refuse") + @Operation(summary = "멘토링 신청 거절", description = "멘토링 신청을 거절한다.") + public ResponseEntity refuseContact(@RequestBody @Valid ContactRequest.ContactRefuseDTO contactRefuseDTO, Error errors, @AuthenticationPrincipal CustomUserDetails userDetails) { + // TO-DO : 멘토링 신청 거절 API 로직 만들기 + contactService.refuseContact(contactRefuseDTO, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); + } + + @PostMapping(value = "/contacts") + @Operation(summary = "멘티의 멘토링 신청", description = "멘토가 작성한 글을 보고, 멘티는 멘토링 신청을 할 수 있다.") + public ResponseEntity createContact(@RequestBody @Valid ContactRequest.ContactCreateDTO contactCreateDTO, Error errors, @AuthenticationPrincipal CustomUserDetails userDetails) { + // TO-DO : 멘토링 신청 API 로직 만들기 + contactService.createContact(contactCreateDTO, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); + } + @DeleteMapping(value = "/contacts") + @Operation(summary = "멘티의 멘토링 신청 취소", description = "멘티는 신청한 멘토링을 취소할 수 있다.") + public ResponseEntity deleteContact(@RequestHeader("contactId") List contactId, Error errors, @AuthenticationPrincipal CustomUserDetails userDetails) { + // TO-DO : 멘토링 신청 취소 API 로직 만들기 + contactService.deleteContact(contactId, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); + } } diff --git a/src/main/java/com/example/demo/mentoring/contact/ContactService.java b/src/main/java/com/example/demo/mentoring/contact/ContactService.java index cac09d1b..f377e130 100644 --- a/src/main/java/com/example/demo/mentoring/contact/ContactService.java +++ b/src/main/java/com/example/demo/mentoring/contact/ContactService.java @@ -1,6 +1,5 @@ package com.example.demo.mentoring.contact; -import com.example.demo.config.errors.exception.Exception400; import com.example.demo.config.errors.exception.Exception401; import com.example.demo.config.errors.exception.Exception404; import com.example.demo.mentoring.MentorPost; @@ -9,7 +8,6 @@ import com.example.demo.mentoring.done.DoneJPARepository; import com.example.demo.user.Role; import com.example.demo.user.User; -import com.example.demo.user.UserJPARepository; import com.example.demo.user.userInterest.UserInterest; import com.example.demo.user.userInterest.UserInterestJPARepository; import lombok.RequiredArgsConstructor; @@ -25,7 +23,6 @@ public class ContactService { private final MentorPostJPARepostiory mentorPostJPARepository; - private final UserJPARepository userJPARepository; private final ContactJPARepository contactJPARepository; private final UserInterestJPARepository userInterestJPARepository; private final DoneJPARepository doneJPARepository; @@ -33,22 +30,21 @@ public class ContactService { /** * contact - mentee 화면에서 mentor 가 작성한 글에 신청을 누른 게시글들을 가져오는 함수 * **/ - public List findAllByMentee(int userId) { + public List findAllByMentee(User mentee) { - return mentorPostJPARepository.findAllByMenteeUserId(userId).stream() - .map(this::createMenteeContactDTO) + return contactJPARepository.findAllByMenteeId(mentee.getId()).stream() + .map(notConnectedRegisterUser -> createMenteeContactDTO(notConnectedRegisterUser.getMentorPost(), notConnectedRegisterUser)) .collect(Collectors.toList()); } // contact - mentee 부분 리팩토링 ( DTO 를 만드는 부분 ) - private ContactResponse.MenteeContactDTO createMenteeContactDTO(MentorPost mentorPost) { - User mentorUser = userJPARepository.findById(mentorPost.getWriter().getId()) - .orElseThrow(() -> new Exception400("해당 사용자가 존재하지 않습니다.")); - List mentorInterests = userInterestJPARepository.findAllById(mentorUser.getId()); + private ContactResponse.ContactDashBoardMenteeDTO createMenteeContactDTO(MentorPost mentorPost, NotConnectedRegisterUser contactUser) { - ContactResponse.MentorDTO mentorDTO = new ContactResponse.MentorDTO(mentorUser, mentorInterests); + List mentorInterests = userInterestJPARepository.findAllById(contactUser.getMenteeUser().getId()); - return new ContactResponse.MenteeContactDTO(mentorPost, mentorDTO); + ContactResponse.ContactMentorDTO contactMentorDTO = new ContactResponse.ContactMentorDTO(contactUser.getMenteeUser(), mentorInterests); + + return new ContactResponse.ContactDashBoardMenteeDTO(mentorPost, contactMentorDTO, contactUser); } // --------------------------------------------------------------------------------------------- @@ -56,82 +52,151 @@ private ContactResponse.MenteeContactDTO createMenteeContactDTO(MentorPost mento /** * contact - mentor 화면에서 post 와 mentee 간 엮인 정보들을 조회해서 가져오는 함수 * **/ - public List findAllByMentor(int userId) { - - User mentorUser = userJPARepository.findById(userId) - .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); - List mentorInterests = userInterestJPARepository.findAllById(mentorUser.getId()); - ContactResponse.MentorDTO mentorDTO = new ContactResponse.MentorDTO(mentorUser, mentorInterests); + public List findAllByMentor(User mentor) { + List mentorInterests = userInterestJPARepository.findAllById(mentor.getId()); + ContactResponse.ContactMentorDTO contactMentorDTO = new ContactResponse.ContactMentorDTO(mentor, mentorInterests); - return mentorPostJPARepository.findAllByWriter(userId).stream() - .map(mentorPost -> createMentorPostDTO(mentorPost, mentorDTO)) + return mentorPostJPARepository.findAllByWriter(mentor.getId()).stream() + .map(mentorPost -> createMentorPostDTO(mentorPost, contactMentorDTO)) .collect(Collectors.toList()); } // MentorPostDTO 생성 로직 - private ContactResponse.MentorPostDTO createMentorPostDTO(MentorPost mentorPost, ContactResponse.MentorDTO mentorDTO) { - List menteeDTOs = contactJPARepository.findAllByMentorPostId(mentorPost.getId()) + private ContactResponse.ContactDashboardMentorDTO createMentorPostDTO(MentorPost mentorPost, ContactResponse.ContactMentorDTO contactMentorDTO) { + List contactMenteeDTOS = contactJPARepository.findAllByMentorPostId(mentorPost.getId()) .stream() .map(this::createMenteeDTO) .collect(Collectors.toList()); - return new ContactResponse.MentorPostDTO(mentorPost, mentorDTO, menteeDTOs); + return new ContactResponse.ContactDashboardMentorDTO(mentorPost, contactMentorDTO, contactMenteeDTOS); } // 매핑 로직 분리 ( menteeDTO 생성 로직 ) - private ContactResponse.MenteeDTO createMenteeDTO(NotConnectedRegisterUser mentee) { + private ContactResponse.ContactMenteeDTO createMenteeDTO(NotConnectedRegisterUser contactUser) { List menteeInterests = userInterestJPARepository - .findAllById(mentee.getMenteeUser().getId()); + .findAllById(contactUser.getMenteeUser().getId()); - return new ContactResponse.MenteeDTO(mentee, menteeInterests); + return new ContactResponse.ContactMenteeDTO(contactUser, menteeInterests); } // contact, done 화면에서 게시글을 조회해서 갯수를 전달해주는 함수 - public ContactResponse.postCountDTO postCountsByMentor(int userId) { + public ContactResponse.PostCountDTO postCountsByMentor(int userId) { // contact 화면에서 게시글을 조회 ( 나중에 where 조건에 state 를 달아야 함 ) int contactCount = mentorPostJPARepository.countContactByMentorId(userId); // done 화면에서 게시글을 조회 int doneCount = mentorPostJPARepository.countDoneByMentorId(userId); - return new ContactResponse.postCountDTO(contactCount, doneCount); + return new ContactResponse.PostCountDTO(contactCount, doneCount); + } + // contact, done 화면에서 게시글을 조회해서 갯수를 전달해주는 함수 ( 멘티 ) + public ContactResponse.PostCountDTO postCountsMyMentee(int userId) { + // contact 화면에서 게시글을 조회 + int contactCount = contactJPARepository.countContactByMenteeId(userId); + // done 화면에서 게시글을 조회 + int doneCount = doneJPARepository.countDoneByMenteeId(userId); + + return new ContactResponse.PostCountDTO(contactCount, doneCount); } @Transactional - public void acceptContact(int id, ContactRequest.AcceptDTO acceptDTO, User user) { - // 예외 처리 - if ( user.getRole() != Role.MENTOR ) { - throw new Exception401("해당 사용자는 멘토가 아닙니다."); - } + public void acceptContact(ContactRequest.ContactAcceptDTO contactAcceptDTO, User mentor) { + isMentor(mentor); - if (id != user.getId() ) { + // dto 예외 처리 + if ( contactAcceptDTO.getMentorId() != mentor.getId() ) { throw new Exception401("올바른 사용자가 아닙니다."); } - int mentorPostId = acceptDTO.getMentorPostId(); + int mentorPostId = contactAcceptDTO.getMentorPostId(); // 현재 멘토가 작성한 글인지 체크 MentorPost mentorPost = mentorPostJPARepository.findById(mentorPostId) .orElseThrow(() -> new Exception404("해당 게시글을 찾을 수 없습니다.")); // ConnectedUser 에 추가 - for ( ContactRequest.AcceptDTO.MentorAndMenteeDTO mentorAndMenteeDTO : acceptDTO.getMentorsAndMentees() ) { - - // 멘토가 현재 유저와 같은지 확인 - if ( mentorAndMenteeDTO.getMentorId() != user.getId() ) { - throw new Exception401("올바른 사용자가 아닙니다."); - } + for ( ContactRequest.ContactAcceptDTO.AcceptMenteeDTO acceptMenteeDTO : contactAcceptDTO.getMentees() ) { // notConnectedRegisterUser 의 state 바꾸기 -> ACCEPT - NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findByMentorPostIdAndMenteeUserId(mentorPostId, mentorAndMenteeDTO.getMenteeId()) + NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findById(acceptMenteeDTO.getMenteeId()) .orElseThrow(() -> new Exception404("해당 사용자를 찾을 수 없습니다.")); - notConnectedRegisterUser.updateStatus(NotConnectedRegisterUser.State.ACCEPT); + notConnectedRegisterUser.updateStatus(ContactStateEnum.ACCEPT); // ConnectedUser 에 save 하기 doneJPARepository.save(new ConnectedUser(mentorPost, notConnectedRegisterUser.getMenteeUser())); } } + + @Transactional + public void refuseContact(ContactRequest.ContactRefuseDTO contactRefuseDTO, User mentor) { + // 예외 처리 + isMentor(mentor); + + // dto 예외 처리 + if ( contactRefuseDTO.getMentorId() != mentor.getId() ) { + throw new Exception401("올바른 사용자가 아닙니다."); + } + + // notConnectedRegisterUser 의 state 바꾸기 -> REFUSE + for ( ContactRequest.ContactRefuseDTO.RefuseMenteeDTO refuseMenteeDTO : contactRefuseDTO.getMentees() ) { + + NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findById(refuseMenteeDTO.getMenteeId()) + .orElseThrow(() -> new Exception404("해당 사용자를 찾을 수 없습니다.")); + + notConnectedRegisterUser.updateStatus(ContactStateEnum.REFUSE); + } + } + + @Transactional + public void createContact(ContactRequest.ContactCreateDTO contactCreateDTO, User mentee) { + // 예외 처리 + isMentee(mentee); + + int mentorPostId = contactCreateDTO.getMentorPostId(); + + // 현재 멘토가 작성한 글인지 체크 + MentorPost mentorPost = mentorPostJPARepository.findById(mentorPostId) + .orElseThrow(() -> new Exception404("해당 게시글을 찾을 수 없습니다.")); + + // notConnectedRegisterUser 에 save 하기 + contactJPARepository.save(new NotConnectedRegisterUser(mentorPost, mentee, ContactStateEnum.AWAIT)); + + } + + @Transactional + public void deleteContact(List contactId, User mentee) { + // 예외 처리 + isMentee(mentee); + + // 해당하는 NotConnectedRegisterUser 가져오기 + for (int contact : contactId) { + NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findById(contact) + .orElseThrow(() -> new Exception404("해당 사용자를 찾을 수 없습니다." )); + + if ( notConnectedRegisterUser.getMenteeUser().getId() != mentee.getId() ) { + throw new Exception401("올바른 사용자가 아닙니다."); + } + // notConnectedRegisterUser delete 요청 보내기 + contactJPARepository.deleteById(notConnectedRegisterUser.getId()); + } + + } + + // 멘토 인증을 위한 메소드 + private void isMentor(User mentor) { + // 예외 처리 + if ( mentor.getRole() != Role.MENTOR ) { + throw new Exception401("해당 사용자는 멘토가 아닙니다."); + } + } + + // 멘티 인증을 위한 메소드 + private void isMentee(User mentee) { + if ( mentee.getRole() != Role.MENTEE ) { + throw new Exception401("해당 사용자는 멘티가 아닙니다."); + } + } } diff --git a/src/main/java/com/example/demo/mentoring/contact/ContactStateConverter.java b/src/main/java/com/example/demo/mentoring/contact/ContactStateConverter.java new file mode 100644 index 00000000..205a995f --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/contact/ContactStateConverter.java @@ -0,0 +1,20 @@ +package com.example.demo.mentoring.contact; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.persistence.AttributeConverter; + +@RequiredArgsConstructor +@Component +public class ContactStateConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(ContactStateEnum attribute) { + return attribute.getValue(); + } + + @Override + public ContactStateEnum convertToEntityAttribute(String dbData) { + return ContactStateEnum.findOf(dbData); + } +} diff --git a/src/main/java/com/example/demo/mentoring/contact/ContactStateEnum.java b/src/main/java/com/example/demo/mentoring/contact/ContactStateEnum.java new file mode 100644 index 00000000..7a34e35e --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/contact/ContactStateEnum.java @@ -0,0 +1,32 @@ +package com.example.demo.mentoring.contact; + +import com.example.demo.mentoring.MentorPostStateEnum; +import io.swagger.models.Contact; +import lombok.Getter; + +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Getter +public enum ContactStateEnum { + ACCEPT("ACCEPT", "ACCEPT"), + REFUSE("REFUSE", "REFUSE"), + AWAIT("AWAIT", "AWAIT"); + + private final String desc; + private final String value; + + ContactStateEnum(String desc, String value) { + this.desc = desc; + this.value = value; + } + + private static final Map descriptions = Collections.unmodifiableMap(Stream.of(values()).collect(Collectors.toMap(ContactStateEnum::getValue, Function.identity()))); + + public static ContactStateEnum findOf(String findValue) { + return descriptions.get(findValue); + } +} diff --git a/src/main/java/com/example/demo/mentoring/contact/NotConnectedRegisterUser.java b/src/main/java/com/example/demo/mentoring/contact/NotConnectedRegisterUser.java index ba5ea63d..e76c4b98 100644 --- a/src/main/java/com/example/demo/mentoring/contact/NotConnectedRegisterUser.java +++ b/src/main/java/com/example/demo/mentoring/contact/NotConnectedRegisterUser.java @@ -2,6 +2,7 @@ import com.example.demo.config.utils.BaseTime; import com.example.demo.mentoring.MentorPost; +import com.example.demo.mentoring.MentorPostStateConverter; import com.example.demo.user.User; import lombok.AccessLevel; import lombok.Builder; @@ -16,8 +17,15 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @Where(clause = "deleted_at IS NULL") -@SQLDelete(sql = "UPDATE not_connected_register_users SET deleted_at = CURRENT_TIMESTAMP, isDeleted = TRUE where id = ?") -@Table(name = "not_connected_register_users") +@SQLDelete(sql = "UPDATE not_connected_register_users SET deleted_at = CURRENT_TIMESTAMP, is_deleted = TRUE where id = ?") +@Table(name = "not_connected_register_users", + indexes = { + @Index(name = "not_connected_register_users_mentor_post_id_idx", columnList = "mentor_post_id"), + @Index(name = "not_connected_register_users_mentee_user_id_idx", columnList = "mentee_user_id") + }, + uniqueConstraints = { + @UniqueConstraint(name = "uk_not_connected_register_user_mentor_post_mentee_user", columnNames = {"mentor_post_id", "mentee_user_id"}) + }) public class NotConnectedRegisterUser extends BaseTime { @Id @@ -30,20 +38,16 @@ public class NotConnectedRegisterUser extends BaseTime { @ManyToOne(fetch = FetchType.LAZY) private User menteeUser; - @Column(nullable = false) - @Enumerated(value = EnumType.STRING) - private State state; + @Convert(converter = ContactStateConverter.class) + @Column(name = "state", nullable = false) + private ContactStateEnum state; - public void updateStatus(State state) { + public void updateStatus(ContactStateEnum state) { this.state = state; } - public enum State { - ACCEPT, REFUSE, AWAIT - } - @Builder - public NotConnectedRegisterUser(MentorPost mentorPost, User menteeUser, State state) { + public NotConnectedRegisterUser(MentorPost mentorPost, User menteeUser, ContactStateEnum state) { this.mentorPost = mentorPost; this.menteeUser = menteeUser; this.state = state; diff --git a/src/main/java/com/example/demo/mentoring/done/ConnectedUser.java b/src/main/java/com/example/demo/mentoring/done/ConnectedUser.java index 779cb7a8..3b5bc0d7 100644 --- a/src/main/java/com/example/demo/mentoring/done/ConnectedUser.java +++ b/src/main/java/com/example/demo/mentoring/done/ConnectedUser.java @@ -16,8 +16,15 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @Where(clause = "deleted_at IS NULL") -@SQLDelete(sql = "UPDATE connected_users SET deleted_at = CURRENT_TIMESTAMP, isDeleted = TRUE where id = ?") -@Table(name = "connected_users") +@SQLDelete(sql = "UPDATE connected_users SET deleted_at = CURRENT_TIMESTAMP, is_deleted = TRUE where id = ?") +@Table(name = "connected_users", + indexes = { + @Index(name = "not_connected_register_users_mentor_post_id_idx", columnList = "mentor_post_id"), + @Index(name = "not_connected_register_users_mentee_user_id_idx", columnList = "mentee_user_id") + }, + uniqueConstraints = { + @UniqueConstraint(name = "uk_connected_user_mentor_post_mentee_user", columnNames = {"mentor_post_id", "mentee_user_id"}) + }) public class ConnectedUser extends BaseTime { @Id diff --git a/src/main/java/com/example/demo/mentoring/done/DoneJPARepository.java b/src/main/java/com/example/demo/mentoring/done/DoneJPARepository.java index 2d58d891..dc5f01a6 100644 --- a/src/main/java/com/example/demo/mentoring/done/DoneJPARepository.java +++ b/src/main/java/com/example/demo/mentoring/done/DoneJPARepository.java @@ -3,6 +3,20 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import java.util.Collection; +import java.util.List; + public interface DoneJPARepository extends JpaRepository { + @Query("SELECT count(*) FROM ConnectedUser cu WHERE cu.menteeUser.id = :userId") + int countDoneByMenteeId(int userId); + + @Query("SELECT cu FROM ConnectedUser cu WHERE cu.menteeUser.id = :id") + List findAllByMenteeId(int id); + + @Query("SELECT cu FROM ConnectedUser cu WHERE cu.mentorPost.id = :id") + List findAllByMentorPostId(int id); + + @Query("SELECT cu FROM ConnectedUser cu WHERE cu.mentorPost.writer.id = :id") + ConnectedUser findByMentorId(int id); } diff --git a/src/main/java/com/example/demo/mentoring/done/DoneResponse.java b/src/main/java/com/example/demo/mentoring/done/DoneResponse.java new file mode 100644 index 00000000..85dabc77 --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/done/DoneResponse.java @@ -0,0 +1,74 @@ +package com.example.demo.mentoring.done; + +import com.example.demo.mentoring.MentorPost; +import com.example.demo.mentoring.contact.ContactResponse; +import com.example.demo.user.Role; +import com.example.demo.user.User; +import com.example.demo.user.userInterest.UserInterest; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.stream.Collectors; + +public class DoneResponse { + @Getter + @Setter + public static class DoneDashBoardDTO { + private int postId; + private String title; + private DoneMentorDTO mentor; + private List mentees; + + public DoneDashBoardDTO(MentorPost mentorPost, DoneMentorDTO mentor, List mentees) { + this.postId = mentorPost.getId(); + this.title = mentorPost.getTitle(); + this.mentor = mentor; + this.mentees = mentees; + } + } + + @Getter @Setter + public static class DoneMentorDTO { + private int mentorId; + private String profileImage; + private String name; + private String country; + private int age; + private Role role; + private List favorites; + + public DoneMentorDTO(User mentor, List userInterests) { + this.mentorId = mentor.getId(); + this.profileImage = mentor.getProfileImage(); + this.name = mentor.getFirstName() + " " + mentor.getLastName(); + this.country = mentor.getCountry(); + this.age = mentor.getAge(); + this.role = mentor.getRole(); + this.favorites = userInterests.stream() + .map(userInterest -> userInterest.getInterest().getCategory()) + .collect(Collectors.toList()); + } + } + @Getter @Setter + public static class DoneMenteeDTO { + private int menteeId; + private String profileImage; + private String name; + private String country; + private int age; + private Role role; + private List favorites; + public DoneMenteeDTO(ConnectedUser connectedUser, List userInterests) { + this.menteeId = connectedUser.getId(); + this.profileImage = connectedUser.getMenteeUser().getProfileImage(); + this.name = connectedUser.getMenteeUser().getFirstName() + " " + connectedUser.getMenteeUser().getLastName(); + this.country = connectedUser.getMenteeUser().getCountry(); + this.age = connectedUser.getMenteeUser().getAge(); + this.role = connectedUser.getMenteeUser().getRole(); + this.favorites = userInterests.stream() + .map(userInterest -> userInterest.getInterest().getCategory()) + .collect(Collectors.toList()); + } + } +} diff --git a/src/main/java/com/example/demo/mentoring/done/DoneRestController.java b/src/main/java/com/example/demo/mentoring/done/DoneRestController.java new file mode 100644 index 00000000..02d4d7f0 --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/done/DoneRestController.java @@ -0,0 +1,31 @@ +package com.example.demo.mentoring.done; + +import com.example.demo.config.auth.CustomUserDetails; +import com.example.demo.config.utils.ApiResponseBuilder; +import com.example.demo.user.Role; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +public class DoneRestController { + + private final DoneService doneService; + + @GetMapping(value = "/contacts/done") + @Operation(summary = "done 화면 조회", description = "멘토, 멘티 화면에 따라 적절한 화면을 보여준다.") + public ResponseEntity findAllContacts(@AuthenticationPrincipal CustomUserDetails userDetails) { + if (userDetails.getUser().getRole().equals(Role.MENTEE)) { + List responseDTO = doneService.findByMentee(userDetails.getUser()); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); + } + List responseDTO = doneService.findByMentor(userDetails.getUser()); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); + } +} diff --git a/src/main/java/com/example/demo/mentoring/done/DoneService.java b/src/main/java/com/example/demo/mentoring/done/DoneService.java new file mode 100644 index 00000000..aac007fb --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/done/DoneService.java @@ -0,0 +1,81 @@ +package com.example.demo.mentoring.done; + +import com.example.demo.mentoring.MentorPost; +import com.example.demo.mentoring.MentorPostJPARepostiory; +import com.example.demo.mentoring.contact.ContactResponse; +import com.example.demo.mentoring.contact.NotConnectedRegisterUser; +import com.example.demo.user.User; +import com.example.demo.user.userInterest.UserInterest; +import com.example.demo.user.userInterest.UserInterestJPARepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Transactional(readOnly = true) +@RequiredArgsConstructor +@Service +public class DoneService { + + private final DoneJPARepository doneJPARepository; + private final MentorPostJPARepostiory mentorPostJPARepository; + private final UserInterestJPARepository userInterestJPARepository; + + /** + * 멘티 : done 화면 조회 기능 + * **/ + public List findByMentee(User mentee) { + return doneJPARepository.findAllByMenteeId(mentee.getId()).stream() + .map(this::createDoneDashBoardDTO) + .collect(Collectors.toList()); + } + + private DoneResponse.DoneDashBoardDTO createDoneDashBoardDTO(ConnectedUser connectedUser) { + MentorPost mentorPost = connectedUser.getMentorPost(); + List mentorInterests = userInterestJPARepository.findAllById(mentorPost.getWriter().getId()); + DoneResponse.DoneMentorDTO doneMentorDTO = new DoneResponse.DoneMentorDTO(mentorPost.getWriter(), mentorInterests); + + List connectedUsers1 = doneJPARepository.findAllByMentorPostId(mentorPost.getId()); + + List doneMenteeDTOS = connectedUsers1.stream() + .map(this::createDoneMenteeDTO) + .collect(Collectors.toList()); + + return new DoneResponse.DoneDashBoardDTO(mentorPost, doneMentorDTO, doneMenteeDTOS); + } + + private DoneResponse.DoneMenteeDTO createDoneMenteeDTO(ConnectedUser connectedUser) { + List menteeInterests = userInterestJPARepository.findAllById(connectedUser.getMenteeUser().getId()); + return new DoneResponse.DoneMenteeDTO(connectedUser, menteeInterests); + } + + public List findByMentor(User mentor) { + List mentorInterests = userInterestJPARepository.findAllById(mentor.getId()); + DoneResponse.DoneMentorDTO doneMentorDTO = new DoneResponse.DoneMentorDTO(mentor, mentorInterests); + + return mentorPostJPARepository.findAllByWriterDone(mentor.getId()).stream() + .map(mentorPost -> createMentorPostDTO(mentorPost, doneMentorDTO)) + .collect(Collectors.toList()); + } + + private DoneResponse.DoneDashBoardDTO createMentorPostDTO(MentorPost mentorPost, DoneResponse.DoneMentorDTO doneMentorDTO) { + List doneMenteeDTOS = doneJPARepository.findAllByMentorPostId(mentorPost.getId()) + .stream() + .map(this::createMenteeDTO) + .collect(Collectors.toList()); + + return new DoneResponse.DoneDashBoardDTO(mentorPost, doneMentorDTO, doneMenteeDTOS); + } + + // 매핑 로직 분리 ( menteeDTO 생성 로직 ) + private DoneResponse.DoneMenteeDTO createMenteeDTO(ConnectedUser connectedUser) { + + List menteeInterests = userInterestJPARepository + .findAllById(connectedUser.getMenteeUser().getId()); + + return new DoneResponse.DoneMenteeDTO(connectedUser, menteeInterests); + } +} diff --git a/src/main/java/com/example/demo/refreshToken/RefreshToken.java b/src/main/java/com/example/demo/refreshToken/RefreshToken.java new file mode 100644 index 00000000..5c954a5c --- /dev/null +++ b/src/main/java/com/example/demo/refreshToken/RefreshToken.java @@ -0,0 +1,36 @@ +package com.example.demo.refreshToken; + +import com.example.demo.user.User; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "refreshTokens") +public class RefreshToken { + @Id + @GeneratedValue + private int id; + + @OneToOne + @JoinColumn(name = "user_id") + private User user; + + @Column(length = 500) + private String refreshToken; + + @Builder + public RefreshToken(User user, String refreshToken) { + this.user = user; + this.refreshToken = refreshToken; + } + + public void updateRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } +} diff --git a/src/main/java/com/example/demo/refreshToken/RefreshTokenJPARepository.java b/src/main/java/com/example/demo/refreshToken/RefreshTokenJPARepository.java new file mode 100644 index 00000000..d0c59a3a --- /dev/null +++ b/src/main/java/com/example/demo/refreshToken/RefreshTokenJPARepository.java @@ -0,0 +1,14 @@ +package com.example.demo.refreshToken; + +import com.example.demo.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; + +public interface RefreshTokenJPARepository extends JpaRepository { + Optional findByUser(User user); + + @Query("select r from RefreshToken r join fetch r.user where r.user.id = :id") + Optional findByUserId(int id); +} diff --git a/src/main/java/com/example/demo/refreshToken/TokenController.java b/src/main/java/com/example/demo/refreshToken/TokenController.java new file mode 100644 index 00000000..c05388e7 --- /dev/null +++ b/src/main/java/com/example/demo/refreshToken/TokenController.java @@ -0,0 +1,27 @@ +package com.example.demo.refreshToken; + +import com.example.demo.config.jwt.JWTTokenProvider; +import com.example.demo.config.utils.ApiResponseBuilder; +import io.swagger.annotations.Api; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +@Api(tags = "Token API") +@RequiredArgsConstructor +@RestController +public class TokenController { + + private final TokenService tokenService; + + @Operation(summary = "Access Token 재발급", description = "Refresh Token을 이용하여 Access Token 재발급") + @GetMapping(value = "/users/refresh") + public ResponseEntity refreshToken(@RequestHeader(value = "Authorization") String refreshToken) { + String accessToken = tokenService.reissueAccessToken(refreshToken); + return ResponseEntity.status(HttpStatus.OK).header(JWTTokenProvider.Header, accessToken).body(ApiResponseBuilder.successWithNoContent()); + } +} diff --git a/src/main/java/com/example/demo/refreshToken/TokenResponse.java b/src/main/java/com/example/demo/refreshToken/TokenResponse.java new file mode 100644 index 00000000..1ae221b9 --- /dev/null +++ b/src/main/java/com/example/demo/refreshToken/TokenResponse.java @@ -0,0 +1,18 @@ +package com.example.demo.refreshToken; + +import lombok.Getter; +import lombok.Setter; + +public class TokenResponse { + @Getter + @Setter + public static class TokenDTO { + private String accessToken; + private String refreshToken; + + public TokenDTO(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + } +} diff --git a/src/main/java/com/example/demo/refreshToken/TokenService.java b/src/main/java/com/example/demo/refreshToken/TokenService.java new file mode 100644 index 00000000..bb1b2118 --- /dev/null +++ b/src/main/java/com/example/demo/refreshToken/TokenService.java @@ -0,0 +1,44 @@ +package com.example.demo.refreshToken; + +import com.example.demo.config.errors.exception.Exception400; +import com.example.demo.config.errors.exception.Exception401; +import com.example.demo.config.jwt.JWTTokenProvider; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +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 java.util.Optional; + +@Transactional +@RequiredArgsConstructor +@Service +public class TokenService { + + private final RefreshTokenJPARepository refreshTokenJPARepository; + private final JWTTokenProvider jwtTokenProvider; + + public String reissueAccessToken(String jwtRefreshToken) { + if (jwtRefreshToken == null || !(jwtRefreshToken.startsWith("Bearer "))) { + throw new Exception401("유효하지 않은 토큰입니다.1"); + } + + String extractedJwtRefreshToken = jwtRefreshToken.replace(JWTTokenProvider.Token_Prefix, ""); + if (jwtTokenProvider.validateToken(extractedJwtRefreshToken)) { + int id = Integer.valueOf(jwtTokenProvider.decodeJwtToken(extractedJwtRefreshToken).get("id").toString()); + RefreshToken refreshTokenInfo = refreshTokenJPARepository.findByUserId(id) + .orElseThrow(() -> new Exception401("유효하지 않은 토큰입니다.2")); + + if (!extractedJwtRefreshToken.equals(refreshTokenInfo.getRefreshToken())) { + throw new Exception401("유효하지 않은 토큰입니다.3"); + } + + String accessToken = JWTTokenProvider.createAccessToken(refreshTokenInfo.getUser()); + return JWTTokenProvider.Token_Prefix + accessToken; + } + return null; + } +} diff --git a/src/main/java/com/example/demo/user/User.java b/src/main/java/com/example/demo/user/User.java index 5ab206c0..a2605dd9 100644 --- a/src/main/java/com/example/demo/user/User.java +++ b/src/main/java/com/example/demo/user/User.java @@ -13,7 +13,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -@Table(name = "user_tb") +@Table(name = "users") public class User extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/example/demo/user/UserResponse.java b/src/main/java/com/example/demo/user/UserResponse.java index ed2b7658..6abc02f5 100644 --- a/src/main/java/com/example/demo/user/UserResponse.java +++ b/src/main/java/com/example/demo/user/UserResponse.java @@ -1,5 +1,6 @@ package com.example.demo.user; +import com.example.demo.refreshToken.TokenResponse; import lombok.Getter; import lombok.Setter; @@ -8,11 +9,11 @@ public class UserResponse { @Setter public static class LoginDTO { private UserDetailDTO userDetailDTO; - private String JWTToken; + private JWTToken jwtToken; - public LoginDTO(User user, String JWTToken) { + public LoginDTO(User user, TokenResponse.TokenDTO token) { this.userDetailDTO = new UserDetailDTO(user); - this.JWTToken = JWTToken; + this.jwtToken = new JWTToken(token); } @Getter @@ -36,6 +37,18 @@ public UserDetailDTO(User user) { this.role = user.getRole(); } } + + @Getter + @Setter + public class JWTToken { + private String accessToken; + private String refreshToken; + + public JWTToken(TokenResponse.TokenDTO token) { + this.accessToken = token.getAccessToken(); + this.refreshToken = token.getRefreshToken(); + } + } } @Getter diff --git a/src/main/java/com/example/demo/user/UserRestController.java b/src/main/java/com/example/demo/user/UserRestController.java index 99b4abfd..eb80f165 100644 --- a/src/main/java/com/example/demo/user/UserRestController.java +++ b/src/main/java/com/example/demo/user/UserRestController.java @@ -1,19 +1,23 @@ package com.example.demo.user; +import com.example.demo.config.auth.CustomUserDetails; import com.example.demo.config.jwt.JWTTokenProvider; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.models.Response; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; @Api(tags = "User API") @RequiredArgsConstructor @@ -26,34 +30,47 @@ public class UserRestController { @PostMapping(value = "/users/emailcheck") public ResponseEntity emailCheck(@RequestBody @Valid UserRequest.EmailCheckDTO requestDTO, Errors errors) { userService.emailCheck(requestDTO); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.successWithNoContent()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } @Operation(summary = "회원가입", description = "회원가입") @PostMapping(value = "/users/signup") public ResponseEntity signup(@RequestBody @Valid UserRequest.SignUpDTO requestDTO, Errors errors) { userService.signup(requestDTO); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.successWithNoContent()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } @Operation(summary = "로그인", description = "로그인") @PostMapping(value = "/users/login") - public ResponseEntity login(@RequestBody @Valid UserRequest.LoginDTO requestDTO, Errors errors) { + public ResponseEntity login(@RequestBody @Valid UserRequest.LoginDTO requestDTO, Errors errors, HttpServletResponse httpServletResponse) throws UnsupportedEncodingException { UserResponse.LoginDTO responseDTO = userService.login(requestDTO); - return ResponseEntity.status(HttpStatus.OK).header(JWTTokenProvider.HEADER, responseDTO.getJWTToken()).body(ApiUtils.success(responseDTO.getUserDetailDTO())); + + String accessToken = JWTTokenProvider.Token_Prefix + responseDTO.getJwtToken().getAccessToken(); + String refreshToken = JWTTokenProvider.Token_Prefix + responseDTO.getJwtToken().getRefreshToken(); + refreshToken = URLEncoder.encode(refreshToken, "utf-8"); + + httpServletResponse.addHeader(JWTTokenProvider.Header, accessToken); + Cookie cookie = new Cookie("RefreshToken", refreshToken); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setPath("/"); + cookie.setMaxAge(60 * 5); + httpServletResponse.addCookie(cookie); + + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.success(responseDTO.getUserDetailDTO())); } @Operation(summary = "마이 페이지 프로필 확인", description = "마이 페이지에서 프로필 확인") - @GetMapping(value = "/profiles/{id}") - public ResponseEntity profile(@PathVariable int id) { - UserResponse.ProfileDTO responseDTO = userService.findProfile(id); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.success(responseDTO)); + @GetMapping(value = {"/profiles", "/profiles/{id}"}) + public ResponseEntity profile(@PathVariable(required = false) Integer id, @AuthenticationPrincipal CustomUserDetails userDetails) { + UserResponse.ProfileDTO responseDTO = userService.findProfile(id, userDetails.getUser()); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.success(responseDTO)); } @Operation(summary = "마이 페이지 프로필 수정", description = "마이 페이지에서 프로필 수정") - @PutMapping(value = "/profiles/{id}") - public ResponseEntity profileUpdate(@PathVariable int id, @RequestBody @Valid UserRequest.ProfileUpdateDTO requestDTO, Errors errors) { - UserResponse.ProfileDTO responseDTO = userService.updateProfile(id, requestDTO); - return ResponseEntity.status(HttpStatus.OK).body(ApiUtils.success(responseDTO)); + @PutMapping(value = "/profiles") + public ResponseEntity profileUpdate(@RequestBody @Valid UserRequest.ProfileUpdateDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { + UserResponse.ProfileDTO responseDTO = userService.updateProfile(userDetails.getUser().getId(), requestDTO); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.success(responseDTO)); } } diff --git a/src/main/java/com/example/demo/user/UserService.java b/src/main/java/com/example/demo/user/UserService.java index f5199f66..7ca3140e 100644 --- a/src/main/java/com/example/demo/user/UserService.java +++ b/src/main/java/com/example/demo/user/UserService.java @@ -5,6 +5,9 @@ import com.example.demo.interest.InterestJPARepository; import com.example.demo.config.errors.exception.Exception400; import com.example.demo.config.jwt.JWTTokenProvider; +import com.example.demo.refreshToken.RefreshToken; +import com.example.demo.refreshToken.RefreshTokenJPARepository; +import com.example.demo.refreshToken.TokenResponse; import com.example.demo.user.userInterest.UserInterest; import com.example.demo.user.userInterest.UserInterestJPARepository; import lombok.RequiredArgsConstructor; @@ -23,6 +26,7 @@ public class UserService { private final PasswordEncoder passwordEncoder; private final UserJPARepository userJPARepository; + private final RefreshTokenJPARepository refreshTokenJPARepository; private final InterestJPARepository interestJPARepository; private final UserInterestJPARepository userInterestJPARepository; @@ -42,6 +46,7 @@ public void signup(UserRequest.SignUpDTO requestDTO) { .country(requestDTO.getCountry()) .introduction(requestDTO.getIntroduction()) .age(requestDTO.getAge()) + .phone(requestDTO.getPhone()) .profileImage(requestDTO.getProfileImage()) .role(requestDTO.getRole()) .build(); @@ -58,6 +63,7 @@ public void signup(UserRequest.SignUpDTO requestDTO) { userInterestJPARepository.saveAll(userInterestList); } + @Transactional public UserResponse.LoginDTO login(UserRequest.LoginDTO requestDTO) { User user = userJPARepository.findByEmail(requestDTO.getEmail()) .orElseThrow(() -> new Exception400("잘못된 이메일입니다.")); @@ -66,12 +72,28 @@ public UserResponse.LoginDTO login(UserRequest.LoginDTO requestDTO) { throw new Exception400("잘못된 비밀번호입니다."); } - return new UserResponse.LoginDTO(user, JWTTokenProvider.create(user)); + TokenResponse.TokenDTO token = JWTTokenProvider.createToken(user); + + Optional refreshTokenInfo = refreshTokenJPARepository.findByUser(user); + if (refreshTokenInfo.isPresent()) { + refreshTokenInfo.get().updateRefreshToken(token.getRefreshToken()); + } else { + RefreshToken newRefreshToken = RefreshToken.builder().user(user).refreshToken(token.getRefreshToken()).build(); + refreshTokenJPARepository.save(newRefreshToken); + } + + return new UserResponse.LoginDTO(user, token); } - public UserResponse.ProfileDTO findProfile(int id) { - User user = userJPARepository.findById(id) - .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + public UserResponse.ProfileDTO findProfile(Integer id, User sessionUser) { + User user; + if (id == null) { + user = userJPARepository.findById(sessionUser.getId()) + .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + } else { + user = userJPARepository.findById(id) + .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + } return new UserResponse.ProfileDTO(user); } @@ -88,6 +110,7 @@ public UserResponse.ProfileDTO updateProfile(int id, UserRequest.ProfileUpdateDT .country(requestDTO.getCountry()) .introduction(requestDTO.getIntroduction()) .age(requestDTO.getAge()) + .phone(requestDTO.getPhone()) .profileImage(requestDTO.getProfileImage()) .role(requestDTO.getRole()) .build(); diff --git a/src/main/java/com/example/demo/user/userInterest/UserInterest.java b/src/main/java/com/example/demo/user/userInterest/UserInterest.java index fec72ff8..b5296fe6 100644 --- a/src/main/java/com/example/demo/user/userInterest/UserInterest.java +++ b/src/main/java/com/example/demo/user/userInterest/UserInterest.java @@ -14,7 +14,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -@Table(name = "user_interest_tb") +@Table(name = "user_interests") public class UserInterest extends BaseTime { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/example/demo/video/VideoRestController.java b/src/main/java/com/example/demo/video/VideoRestController.java index 95b8788f..8c1e8ee8 100644 --- a/src/main/java/com/example/demo/video/VideoRestController.java +++ b/src/main/java/com/example/demo/video/VideoRestController.java @@ -1,7 +1,7 @@ package com.example.demo.video; import com.example.demo.config.auth.CustomUserDetails; -import com.example.demo.config.utils.ApiUtils; +import com.example.demo.config.utils.ApiResponseBuilder; import io.swagger.annotations.Api; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -23,24 +23,24 @@ public class VideoRestController { @GetMapping("/videos/interest") public ResponseEntity getUserCategoryVideo(@AuthenticationPrincipal CustomUserDetails userDetails){ List responseDTOs = videoService.findUserCategory(userDetails.getUser().getId()); - return ResponseEntity.ok(ApiUtils.success(responseDTOs)); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTOs)); } @GetMapping("/videos/main") public ResponseEntity getCategoryFilterVideo(@RequestParam(value = "category", defaultValue = "0") int id) { List responseDTOs = videoService.findAllVideo(id); - return ResponseEntity.ok(ApiUtils.success(responseDTOs)); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTOs)); } @GetMapping("/videos/{id}") public ResponseEntity getVideoId(@PathVariable int id) { VideoResponse.VideoResponseDTO responseDTO = videoService.findVideo(id); - return ResponseEntity.ok(ApiUtils.success(responseDTO)); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } @GetMapping("/videos/history") public ResponseEntity getVideoHistory(@RequestParam(value = "page", defaultValue = "0") Integer page, @AuthenticationPrincipal CustomUserDetails userDetails) { List responseDTO = videoService.findHistoryVideo(page, userDetails.getUser().getId()); - return ResponseEntity.ok(ApiUtils.success(responseDTO)); + return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index c74a527d..cd45933e 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -23,5 +23,5 @@ spring: default_batch_fetch_size: 100 open-in-view: false mvc: - pathmatch: - matching-strategy: ant_path_matcher # spring 2.6 ↑ + path match: + matching-strategy: ant_path_matcher # spring 2.6 ↑ \ No newline at end of file diff --git a/src/main/resources/db/teardown.sql b/src/main/resources/db/teardown.sql index d5685160..99c9d37c 100644 --- a/src/main/resources/db/teardown.sql +++ b/src/main/resources/db/teardown.sql @@ -1,14 +1,14 @@ SET REFERENTIAL_INTEGRITY FALSE; -TRUNCATE TABLE user_tb; -TRUNCATE TABLE interest_tb; -TRUNCATE TABLE mentor_post_tb; -TRUNCATE TABLE user_interest_tb; -TRUNCATE TABLE not_connected_register_user_tb; -TRUNCATE TABLE connected_user_tb; +TRUNCATE TABLE users; +TRUNCATE TABLE interests; +TRUNCATE TABLE mentor_posts; +TRUNCATE TABLE user_interests; +TRUNCATE TABLE not_connected_register_users; +TRUNCATE TABLE connected_users; SET REFERENTIAL_INTEGRITY TRUE; -- user Table -INSERT INTO user_tb (created_at, first_name, last_name, email, password, country, introduction, age, profile_image, role, phone) VALUES +INSERT INTO users (created_at, first_name, last_name, email, password, country, introduction, age, profile_image, role, phone) VALUES (NOW(), 'John', 'Doe', 'john@example.com', '{bcrypt}$2a$10$8H0OT8wgtALJkig6fmypi.Y7jzI5Y7W9PGgRKqnVeS2cLWGifwHF2', 'USA', 'Hello, I am John.', 25, 'profile.jpg', 'MENTOR', '010-0000-0000'), (NOW(), 'Alice', 'Smith', 'alice.smith@example.com', '{bcrypt}$2a$10$8H0OT8wgtALJkig6fmypi.Y7jzI5Y7W9PGgRKqnVeS2cLWGifwHF2', 'Canada', 'I love painting.', 25, 'image2.jpg', 'MENTOR', '010-0000-0000'), (NOW(), 'Admin', 'Admin', 'admin@example.com', '{bcrypt}$2a$10$8H0OT8wgtALJkig6fmypi.Y7jzI5Y7W9PGgRKqnVeS2cLWGifwHF2', 'USA', 'I am an admin user.', 35, 'admin.jpg', 'MENTEE', '010-0000-0000'), @@ -17,14 +17,14 @@ INSERT INTO user_tb (created_at, first_name, last_name, email, password, country (NOW(), 'Adminnnn', 'Adminsonnnnn', 'admin33333@example.com', '{bcrypt}$2a$10$8H0OT8wgtALJkig6fmypi.Y7jzI5Y7W9PGgRKqnVeS2cLWGifwHF2', 'USA', 'I am the admin.', 35, 'admin.jpg', 'MENTEE', '010-0000-0000'); -- interst Table -INSERT INTO interest_tb (created_at, category) VALUES +INSERT INTO interests (created_at, category) VALUES (NOW(), 'IDOL'), (NOW(), 'Game'), (NOW(), 'K-POP'), (NOW(), 'Sports'); -- mentorPost Table -INSERT INTO mentor_post_tb (created_at, writer_id, title, content, state) VALUES +INSERT INTO mentor_posts (created_at, writer_id, title, content, state) VALUES (NOW(), 1, 'Teaching Programming', 'I can teach you how to code.', 'ACTIVE'), (NOW(), 1, 'Art Workshop', 'Let''s create beautiful art together.', 'DONE'), (NOW(), 2, 'Software Development Mentorship', 'I can mentor you in software development.', 'ACTIVE'), @@ -42,19 +42,21 @@ INSERT INTO mentor_post_tb (created_at, writer_id, title, content, state) VALUES -- notConnectedRegisterUser Table -INSERT INTO not_connected_register_user_tb (created_at, mentor_post_id, mentee_user_id, state) VALUES +INSERT INTO not_connected_register_users (created_at, mentor_post_id, mentee_user_id, state) VALUES (NOW(), 1, 3, 'AWAIT'), + (NOW(), 3, 3, 'AWAIT'), (NOW(), 1, 4, 'ACCEPT'), (NOW(), 3, 5, 'AWAIT'); -- connectedUser Table -INSERT INTO connected_user_tb (created_at, mentor_post_id, mentee_user_id) VALUES +INSERT INTO connected_users (created_at, mentor_post_id, mentee_user_id) VALUES (NOW(), 2, 3), + (NOW(), 3, 3), (NOW(), 2, 5), (NOW(), 2, 6); -- userInterest Table -INSERT INTO user_interest_tb (created_at, user_id, interest_id) VALUES +INSERT INTO user_interests (created_at, user_id, interest_id) VALUES (NOW(), 1, 1), (NOW(), 1, 2), (NOW(), 2, 1), diff --git a/src/test/java/com/example/demo/contactTest.java b/src/test/java/com/example/demo/contactTest.java index 6c1d8dd2..8f73b27f 100644 --- a/src/test/java/com/example/demo/contactTest.java +++ b/src/test/java/com/example/demo/contactTest.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithUserDetails; import org.springframework.test.context.ActiveProfiles; @@ -39,10 +38,29 @@ public class contactTest extends RestDoc { @Autowired private DoneJPARepository doneJPARepository; + @Test + @WithUserDetails("admin@example.com") + @DisplayName("멘티 기준 화면 조회 테스트 코드") + void contactMenteeTest() throws Exception { + // given + + // when + ResultActions resultActions = mvc.perform( + get("/contacts") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + } + @Test @WithUserDetails("john@example.com") - @DisplayName("contact - mentor") - void contactTest() throws Exception { + @DisplayName("멘토 기준 화면 조회 테스트 코드") + void contactMentorTest() throws Exception { // given @@ -59,10 +77,10 @@ void contactTest() throws Exception { resultActions.andExpect(jsonPath("$.status").value("success")); } - + @Test @WithUserDetails("john@example.com") - @DisplayName("contact, done - count") + @DisplayName("멘토 기준 게시글 갯수 조회 테스트 코드") void countTest() throws Exception { // when @@ -78,19 +96,78 @@ void countTest() throws Exception { resultActions.andExpect(jsonPath("$.status").value("success")); } + @Test + @WithUserDetails("admin@example.com") + @DisplayName("멘티 기준 게시글 조회 테스트 코드") + void countByMenteeTest() throws Exception { + // given + + // when + ResultActions resultActions = mvc.perform( + get("/contacts/postCounts") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + } + + @Test + @WithUserDetails("john@example.com") + @DisplayName("멘토 : 신청 거부 기능 테스트 코드") + void contactRefuseTest() throws Exception { + // given + ContactRequest.ContactRefuseDTO requestDTOs = new ContactRequest.ContactRefuseDTO(); + requestDTOs.setMentorPostId(1); + requestDTOs.setMentorId(1); + + List refuseMenteeDTOS = new ArrayList<>(); + refuseMenteeDTOS.add(new ContactRequest.ContactRefuseDTO.RefuseMenteeDTO(1)); + + requestDTOs.setMentees(refuseMenteeDTOS); + + String requestBody = om.writeValueAsString(requestDTOs); + + System.out.println("테스트 : "+requestBody); + + // when + ResultActions result = mvc.perform( + MockMvcRequestBuilders.patch("/contacts/refuse") + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + ); + String responseBody = result.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // 테스트가 잘 됐는지 ( 값이 잘 바뀌는지 확인 ) + NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findById(1) + .orElseThrow(() -> new Exception404("해당 사용자를 찾을 수 없습니다.")); + + System.out.println("state 확인 : " + notConnectedRegisterUser.getState()); + // then + // verify + result.andExpect(jsonPath("$.status").value("success")); // 성공 테스트 확인 + // 값이 잘 들어가는지 확인 + Assertions.assertThat(notConnectedRegisterUser.getState()).isEqualTo(ContactStateEnum.REFUSE); + } + @Test @WithUserDetails("john@example.com") - @DisplayName("contact - Accept - Test") + @DisplayName("멘토 : 신청 수락 테스트 코드") void contactAccpetTest() throws Exception { // given - ContactRequest.AcceptDTO requestDTOs = new ContactRequest.AcceptDTO(); + ContactRequest.ContactAcceptDTO requestDTOs = new ContactRequest.ContactAcceptDTO(); requestDTOs.setMentorPostId(1); + requestDTOs.setMentorId(1); - List mentorAndMenteeDTOs = new ArrayList<>(); - mentorAndMenteeDTOs.add(new ContactRequest.AcceptDTO.MentorAndMenteeDTO(1, 3)); + List acceptMenteeDTOS = new ArrayList<>(); + acceptMenteeDTOS.add(new ContactRequest.ContactAcceptDTO.AcceptMenteeDTO(1)); - requestDTOs.setMentorsAndMentees(mentorAndMenteeDTOs); + requestDTOs.setMentees(acceptMenteeDTOS); String requestBody = om.writeValueAsString(requestDTOs); @@ -98,7 +175,7 @@ void contactAccpetTest() throws Exception { // when ResultActions result = mvc.perform( - MockMvcRequestBuilders.post("/contacts/1/accept") + MockMvcRequestBuilders.post("/contacts/accept") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) ); @@ -106,7 +183,7 @@ void contactAccpetTest() throws Exception { System.out.println("테스트 : "+responseBody); // 테스트가 잘 됐는지 ( 값이 잘 바뀌는지 확인 ) - NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findByMentorPostIdAndMenteeUserId(1, 3) + NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findById(1) .orElseThrow(() -> new Exception404("해당 사용자를 찾을 수 없습니다.")); System.out.println("state 확인 : " + notConnectedRegisterUser.getState()); @@ -120,7 +197,62 @@ void contactAccpetTest() throws Exception { // verify result.andExpect(jsonPath("$.status").value("success")); // 성공 테스트 확인 // 값이 잘 들어가는지 확인 - Assertions.assertThat(notConnectedRegisterUser.getState()).isEqualTo(NotConnectedRegisterUser.State.ACCEPT); + Assertions.assertThat(notConnectedRegisterUser.getState()).isEqualTo(ContactStateEnum.ACCEPT); Assertions.assertThat(connectedUser.getId()).isEqualTo(4); } + + @Test + @WithUserDetails("jane@example.com") + @DisplayName("멘티 : 신청 생성 테스트 코드") + void createTest() throws Exception { + // given + ContactRequest.ContactCreateDTO contactCreateDTO = new ContactRequest.ContactCreateDTO(); + // mentor, mentorPost, mentee id 지정 + contactCreateDTO.setMentorPostId(3); + contactCreateDTO.setMentorId(2); + contactCreateDTO.setMenteeId(4); + + String requestBody = om.writeValueAsString(contactCreateDTO); + + System.out.println("테스트 requestBody : "+requestBody); + + // when + ResultActions result = mvc.perform( + MockMvcRequestBuilders.post("/contacts") + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + ); + String responseBody = result.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 responseBody : "+responseBody); + + // 테스트가 잘 됐는지 ( 값이 잘 바뀌는지 확인 ) + NotConnectedRegisterUser notConnectedRegisterUser = contactJPARepository.findById(5) + .orElseThrow(() -> new Exception404("해당 사용자를 찾을 수 없습니다.")); + + // then + // verify + result.andExpect(jsonPath("$.status").value("success")); // 성공 테스트 확인 + // 값이 잘 들어가는지 확인 + Assertions.assertThat(notConnectedRegisterUser.getState()).isEqualTo(ContactStateEnum.AWAIT); + + } + + @Test + @WithUserDetails("admin@example.com") + @DisplayName("멘티 : 신청 취소 테스트 코드") + void deleteTest() throws Exception { + // given + + // when + ResultActions result = mvc.perform( + MockMvcRequestBuilders.delete("/contacts") + .header("contactId", 1,2) + .contentType(MediaType.APPLICATION_JSON) + ); + String responseBody = result.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 responseBody : "+responseBody); + + // then + result.andExpect(jsonPath("$.status").value("success")); // 성공 테스트 확인 + } } diff --git a/src/test/java/com/example/demo/doneTest.java b/src/test/java/com/example/demo/doneTest.java new file mode 100644 index 00000000..9f7d7629 --- /dev/null +++ b/src/test/java/com/example/demo/doneTest.java @@ -0,0 +1,57 @@ +package com.example.demo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.ResultActions; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest +@ActiveProfiles("test") +@Sql("classpath:db/teardown.sql") +public class doneTest extends RestDoc { + @Test + @WithUserDetails("admin@example.com") + @DisplayName("멘티 기준 화면 조회 테스트 코드") + void contactMenteeTest() throws Exception { + // given + + // when + ResultActions resultActions = mvc.perform( + get("/contacts/done") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + } + + @Test + @WithUserDetails("john@example.com") + @DisplayName("멘토 기준 화면 조회 테스트 코드") + void contactMentorTest() throws Exception { + + // given + + // when + ResultActions resultActions = mvc.perform( + get("/contacts/done") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + + } +} diff --git a/src/test/java/com/example/demo/mentoringtest/MentoringTest.java b/src/test/java/com/example/demo/mentoringtest/MentoringTest.java deleted file mode 100644 index 5b4cc8b8..00000000 --- a/src/test/java/com/example/demo/mentoringtest/MentoringTest.java +++ /dev/null @@ -1,335 +0,0 @@ -package com.example.demo.mentoringtest; - - -import com.example.demo.RestDoc; -import com.example.demo.config.errors.exception.Exception400; -import com.example.demo.config.errors.exception.Exception404; -import com.example.demo.config.utils.StateEnum; -import com.example.demo.interest.Interest; -import com.example.demo.interest.InterestJPARepository; -import com.example.demo.mentoring.*; -import com.example.demo.mentoring.contact.ContactJPARepository; -import com.example.demo.mentoring.contact.NotConnectedRegisterUser; -import com.example.demo.user.Role; -import com.example.demo.user.User; -import com.example.demo.user.UserJPARepository; -import com.example.demo.user.userInterest.UserInterest; -import com.example.demo.user.userInterest.UserInterestJPARepository; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.assertj.core.api.Assertions; -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.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.test.context.support.WithUserDetails; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.jdbc.Sql; -import org.springframework.test.web.servlet.ResultActions; - - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; - -@SpringBootTest -@AutoConfigureMockMvc -@ActiveProfiles("test") -public class MentoringTest extends RestDoc { - @Autowired - private UserJPARepository userJPARepository; - @Autowired - private MentorPostJPARepostiory mentorPostJPARepostiory; - @Autowired - private InterestJPARepository interestJPARepository; - @Autowired - private MentorPostService mentorPostService; - @Autowired - private UserInterestJPARepository userInterestJPARepository; - @Autowired - private ContactJPARepository contactJPARepository; - - @Autowired - private ObjectMapper om; - - - @Test - @DisplayName("CreateMentorPostTest") - void CreateMentorPostTest() { - - //given - User writer = User.builder() - .email("john@example.com") - .password("asdf1234!") - .firstName("Jin") - .lastName("Seung") - .country("Korea") - .age(21) - .role(Role.MENTOR) - .phone("010-0000-0000") - .build(); - - //when - MentorPost mentorPost = MentorPost.builder() - .writer(writer) - .title("title") - .content("content") - .build(); - - MentorPost mentorPost2 = MentorPost.builder() - .writer(writer) - .title("title2") - .content("content2") - .build(); - - // then -// userJPARepository.save(writer); -// mentorPostJPARepostiory.save(mentorPost); -// mentorPostJPARepostiory.save(mentorPost2); - - userJPARepository.save(writer); - MentorPostRequest.CreateDTO mentorPostRequest = new MentorPostRequest.CreateDTO(); - mentorPostRequest.setTitle("title"); - mentorPostRequest.setContent("content"); - mentorPostService.createMentorPost(mentorPostRequest, writer); - - MentorPost mentorPostFind = mentorPostJPARepostiory.findById(1) - .orElseThrow(() -> new Exception404("해당 게시글이 존재하지 않습니다.")); - Assertions.assertThat(1) - .isEqualTo(mentorPostFind.getId()); - Assertions.assertThat(mentorPostRequest.getTitle()) - .isEqualTo(mentorPostFind.getTitle()); - Assertions.assertThat(mentorPostRequest.getContent()) - .isEqualTo(mentorPostFind.getContent()); - } - - - - @Test - @DisplayName("findMentorPostTest") - void findMentorPostSaveTest() { - //given - User mentor = User.builder() - .email("anjdal6612312364@gmail.com") - .password("as123df1234!") - .firstName("Jin123") - .lastName("Seun123g") - .country("Korea") - .age(21) - .role(Role.MENTOR) - .phone("010-0000-0000") - .build(); - - User mentee_One = User.builder() - .email("anjda22l6664@gmail.com") - .password("asdf221234!") - .firstName("mentee1") - .lastName("Seung11") - .country("Korea") - .age(21) - .role(Role.MENTEE) - .phone("010-0000-0000") - .build(); - - User mentee_Two = User.builder() - .email("anjdal66111164@gmail.com") - .password("asdf122222234!") - .firstName("mentee2") - .lastName("Seung22") - .country("Korea") - .age(21) - .role(Role.MENTEE) - .phone("010-0000-0000") - .build(); - - Interest interest1 = Interest.builder() - .category("test1") - .build(); - - Interest interest2 = Interest.builder() - .category("test2") - .build(); - - Interest interest3 = Interest.builder() - .category("test3") - .build(); - - UserInterest userInterest1 = UserInterest.builder() - .user(mentor) - .interest(interest2) - .build(); - - UserInterest userInterest2 = UserInterest.builder() - .user(mentee_One) - .interest(interest2) - .build(); - - UserInterest userInterest3 = UserInterest.builder() - .user(mentee_One) - .interest(interest3) - .build(); - - UserInterest userInterest4 = UserInterest.builder() - .user(mentee_Two) - .interest(interest1) - .build(); - - UserInterest userInterest5 = UserInterest.builder() - .user(mentee_Two) - .interest(interest2) - .build(); - - UserInterest userInterest6 = UserInterest.builder() - .user(mentor) - .interest(interest3) - .build(); - - MentorPost mentorPost3 = MentorPost.builder() - .writer(mentor) - .title("title") - .content("content") - .build(); - - NotConnectedRegisterUser menteeNotConnected1 = NotConnectedRegisterUser.builder() - .mentorPost(mentorPost3) - .menteeUser(mentee_One) - .state(NotConnectedRegisterUser.State.AWAIT) - .build(); - - NotConnectedRegisterUser menteeNotConnected2 = NotConnectedRegisterUser.builder() - .mentorPost(mentorPost3) - .menteeUser(mentee_Two) - .state(NotConnectedRegisterUser.State.AWAIT) - .build(); - - //when - interestJPARepository.save(interest1); - interestJPARepository.save(interest2); - interestJPARepository.save(interest3); - userJPARepository.save(mentor); - userJPARepository.save(mentee_One); - userJPARepository.save(mentee_Two); - mentorPostJPARepostiory.save(mentorPost3); - userInterestJPARepository.save(userInterest1); - userInterestJPARepository.save(userInterest2); - userInterestJPARepository.save(userInterest3); - userInterestJPARepository.save(userInterest4); - userInterestJPARepository.save(userInterest5); - userInterestJPARepository.save(userInterest6); - contactJPARepository.save(menteeNotConnected1); - contactJPARepository.save(menteeNotConnected2); - - } - - @Test - @DisplayName("updateMentorPostTest") - void updateMentorPostTest() - { - User writer = User.builder() - .email("anjdal6664@gmail.com") - .password("asdf1234!") - .firstName("Jin") - .lastName("Seung") - .country("Korea") - .age(21) - .role(Role.MENTOR) - .phone("010-0000-0000") - .build(); - - MentorPostRequest.CreateDTO mentorPostRequest = new MentorPostRequest.CreateDTO(); - mentorPostRequest.setTitle("tittttttle"); - mentorPostRequest.setContent("content"); - - MentorPostRequest.CreateDTO mentorPostUpdated = new MentorPostRequest.CreateDTO(); - mentorPostUpdated.setTitle("updated!!!!"); - mentorPostUpdated.setContent("contenttt"); - - userJPARepository.save(writer); - mentorPostService.createMentorPost(mentorPostRequest, writer); - mentorPostService.updateMentorPost(mentorPostUpdated,2); - - MentorPost mentorPostFind = mentorPostJPARepostiory.findById(2) - .orElseThrow(() -> new Exception404("해당 게시글이 존재하지 않습니다.")); - Assertions.assertThat(2) - .isEqualTo(mentorPostFind.getId()); - Assertions.assertThat(mentorPostUpdated.getTitle()) - .isEqualTo(mentorPostFind.getTitle()); - Assertions.assertThat(mentorPostUpdated.getContent()) - .isEqualTo(mentorPostFind.getContent()); - } - - @WithUserDetails(value = "john@example.com") - @Test - public void CreateMentorPostTestMVC() throws Exception { - - MentorPostRequest.CreateDTO createDTO = new MentorPostRequest.CreateDTO(); - createDTO.setTitle("asfd"); - createDTO.setContent("afaffafa"); - - String requestBody = om.writeValueAsString(createDTO); - - // when - ResultActions resultActions = mvc.perform( - post("/mentorings/post") - .content(requestBody) - .contentType(MediaType.APPLICATION_JSON_VALUE) - ); - - String responseBody = resultActions.andReturn().getResponse().getContentAsString(); - System.out.println("테스트 : " + responseBody); - - // verify - //resultActions.andExpect(jsonPath("$.success").value("true")); - //resultActions.andDo(MockMvcResultHandlers.print()).andDo(document); - } - - @Test - public void GetMentorPostTestMVC() throws Exception { - - int id = 2; - - // when - ResultActions resultActions = mvc.perform( - get("/mentorings/post") - ); - - // console - String responseBody = resultActions.andReturn().getResponse().getContentAsString(); - System.out.println("테스트 : "+responseBody); - } - - @Test - @DisplayName("DeleteTest") - public void DeleteMentorPost() throws Exception{ - int id = 2; - mentorPostService.deleteMentorPost(id); - - MentorPost mentorPostFind = mentorPostJPARepostiory.findById(2) - .orElse(null); - assertNull(mentorPostFind); - } - - @Test - @DisplayName("DoneTest") - public void PatchDoneMentorPost() throws Exception{ - int id = 1; - MentorPostRequest.StateDTO stateDTO = new MentorPostRequest.StateDTO(); - stateDTO.setStateEnum(StateEnum.DONE); - mentorPostService.changeMentorPostStatus(stateDTO, id); - } - - @Test - void mentorPostServiceTest() throws Exception { - List mentorPostFind = mentorPostService.findAllMentorPostWithTimeStamp(); - - String responseBody = om.writeValueAsString(mentorPostFind); - - System.out.println("전체조회테스트 : " + responseBody); - } -} diff --git a/src/test/java/com/example/demo/mentoringtest/MentoringTest2.java b/src/test/java/com/example/demo/mentoringtest/MentoringTest2.java new file mode 100644 index 00000000..9a19e32c --- /dev/null +++ b/src/test/java/com/example/demo/mentoringtest/MentoringTest2.java @@ -0,0 +1,269 @@ +package com.example.demo.mentoringtest; + +import com.example.demo.RestDoc; +import com.example.demo.config.errors.exception.Exception400; +import com.example.demo.mentoring.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.ResultActions; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest +@ActiveProfiles("test") +@Sql("classpath:db/teardown.sql") +public class MentoringTest2 extends RestDoc { + + @Autowired + private ObjectMapper om; + + @Autowired + private MentorPostJPARepostiory mentorPostJPARepostiory; + + @Test + @DisplayName("멘토링 화면 조회 테스트 코드") + void MentoringTestByMentor() throws Exception { + // given + + // when + ResultActions resultActions = mvc.perform( + get("/mentorings/post") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + } + + @Test + @DisplayName("카테고리를 적용한 멘토링 화면 조회 테스트 코드 : Title") + void MentoringTestByCategoryTitle() throws Exception { + // given + + // when + ResultActions resultActions = mvc.perform( + get("/mentorings/post") + .param("category", "TITLE") + .param("search", "Teaching") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + } + + @Test + @DisplayName("카테고리를 적용한 멘토링 화면 조회 테스트 코드 : Writer") + void MentoringTestByCategoryWriter() throws Exception { + // given + + // when + ResultActions resultActions = mvc.perform( + get("/mentorings/post") + .param("category", "WRITER") + .param("search", "John") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + } + + @Test + @DisplayName("카테고리를 적용한 멘토링 화면 조회 테스트 코드 : Interest") + void MentoringTestByCategoryInterest() throws Exception { + // given + + // when + ResultActions resultActions = mvc.perform( + get("/mentorings/post") + .param("category", "INTEREST") + .param("search", "K-POP") + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + } + + @Test + @DisplayName("세부 페이지 조회 테스트") + void MentoringTestUpdate() throws Exception { + // given + int id = 1; + + // when + ResultActions resultActions = mvc.perform( + get("/mentorings/post/" + id) + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // verify + resultActions.andExpect(jsonPath("$.status").value("success")); + + } + + @Test + @WithUserDetails("john@example.com") + @DisplayName("게시글 수정 테스트") + void MentoringTestDetails() throws Exception { + // given + int pid = 1; + + // requestDTO : title, content + MentorPostRequest.CreateDTO requestDTO = new MentorPostRequest.CreateDTO(); + requestDTO.setTitle("바뀐 제목111"); + requestDTO.setContent("바뀐 내용111"); + + String requestBody = om.writeValueAsString(requestDTO); + + System.out.println("테스트 : "+requestBody); + + // when + ResultActions resultActions = mvc.perform( + put("/mentorings/post/" + pid) + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // pid 에 해당하는 게시글 조회 + MentorPost mentorPost = mentorPostJPARepostiory.findById(pid).orElseThrow( + () -> new Exception400("해당 게시글이 없습니다.")); + + // 데이터 확인 + System.out.println(mentorPost.getTitle()); + System.out.println(mentorPost.getContent()); + + // 조회한 게시글의 제목과 내용이 일치하는지 확인 + resultActions.andExpect(jsonPath("$.status").value("success")); + + } + + @Test + @WithUserDetails("john@example.com") + @DisplayName("게시글 삭제 테스트 코드") + void MentoringTestDelete() throws Exception { + // given + int pid = 1; + + // when + ResultActions resultActions = mvc.perform( + delete("/mentorings/post/" + pid) + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // pid 에 해당하는 게시글 조회 + MentorPost mentorPost = mentorPostJPARepostiory.findById(pid).orElse(null); + + // 조회한 게시글의 제목과 내용이 일치하는지 확인 + resultActions.andExpect(jsonPath("$.status").value("success")); + assertNull(mentorPost); + } + + @Test + @WithUserDetails("john@example.com") + @DisplayName("게시글 Done 테스트 코드") + void MentoringTestDone() throws Exception { + // given + int pid = 1; + // request + MentorPostRequest.StateDTO requestDTO = new MentorPostRequest.StateDTO(); + requestDTO.setMentorPostStateEnum(MentorPostStateEnum.DONE); + + String requestBody = om.writeValueAsString(requestDTO); + + System.out.println("테스트 : "+requestBody); + + // when + ResultActions resultActions = mvc.perform( + patch("/mentorings/post/" + pid + "/done") + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // pid 에 해당하는 게시글 조회 + MentorPost mentorPost = mentorPostJPARepostiory.findById(pid).orElseThrow( + () -> new Exception400("해당 게시글이 없습니다.")); + + System.out.println(mentorPost.getState()); + + // 조회한 게시글의 제목과 내용이 일치하는지 확인 + resultActions.andExpect(jsonPath("$.status").value("success")); + assertEquals(mentorPost.getState(), MentorPostStateEnum.DONE); + } + + @Test + @WithUserDetails("admin@example.com") + @DisplayName("멘티가 게시글을 수정하려고 하는 경우 테스트") + void MentoringTest2() throws Exception { + int pid = 1; + + // requestDTO : title, content + MentorPostRequest.CreateDTO requestDTO = new MentorPostRequest.CreateDTO(); + requestDTO.setTitle("바뀐 제목111"); + requestDTO.setContent("바뀐 내용111"); + + String requestBody = om.writeValueAsString(requestDTO); + + System.out.println("테스트 : "+requestBody); + + // when + ResultActions resultActions = mvc.perform( + put("/mentorings/post/" + pid) + .content(requestBody) + .contentType(MediaType.APPLICATION_JSON) + ); + + // console + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + System.out.println("테스트 : "+responseBody); + + // pid 에 해당하는 게시글 조회 + MentorPost mentorPost = mentorPostJPARepostiory.findById(pid).orElseThrow( + () -> new Exception400("해당 게시글이 없습니다.")); + + // 데이터 확인 + System.out.println(mentorPost.getTitle()); + System.out.println(mentorPost.getContent()); + + // 조회한 게시글의 제목과 내용이 일치하는지 확인 + resultActions.andExpect(jsonPath("$.status").value("error")); + } + +}