diff --git a/build.gradle b/build.gradle index eda665b4..ecd58a3f 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,10 @@ dependencies { 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' + + // LocalDate 직렬화 및 역직렬화 + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'org.springframework.boot:spring-boot-devtools' 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 e90edb21..c96e09d2 100644 --- a/src/main/java/com/example/demo/config/jwt/JWTTokenProvider.java +++ b/src/main/java/com/example/demo/config/jwt/JWTTokenProvider.java @@ -7,6 +7,8 @@ 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 io.jsonwebtoken.*; import io.jsonwebtoken.security.SignatureException; import lombok.RequiredArgsConstructor; @@ -15,6 +17,7 @@ import org.springframework.stereotype.Component; import java.util.Date; +import java.util.List; import java.util.Optional; @RequiredArgsConstructor @@ -26,18 +29,17 @@ public class JWTTokenProvider { public static final String SecretKey = "a2FrYW8tdGVjaC1jYW1wdXMtcHJvamVjdC1nYXJkZW4tc3ByaW5nLXNlY3VyaXR5LWp3dC10b2tlbi1zZWNyZXQta2V5"; public static final int AccessTokenValidTime = 1000 * 60 * 5; // 5분 - public static final int RefreshTokenValidTime = 1000 * 60 * 60 * 24; // 1일 + public static final int RefreshTokenValidTime = 1000 * 60 * 60 * 24 * 7; // 1주일 private final CustomUserDetailService userDetailService; - private final RefreshTokenJPARepository refreshTokenJPARepository; - public static TokenResponse.TokenDTO createToken(User user) { - String accessToken = createAccessToken(user); + public static TokenResponse.TokenDTO createToken(User user, List userCategoryList) { + String accessToken = createAccessToken(user, userCategoryList); String refreshToken = createRefreshToken(user); return new TokenResponse.TokenDTO(accessToken, refreshToken); } - public static String createAccessToken(User user) { + public static String createAccessToken(User user, List userCategoryList) { String accessToken = Jwts.builder() .setSubject(user.getEmail()) .claim("id", user.getId()) @@ -46,10 +48,11 @@ public static String createAccessToken(User user) { .claim("email", user.getEmail()) .claim("country", user.getCountry()) .claim("introduction", user.getIntroduction()) - .claim("age", user.getAge()) + .claim("birthDate", user.getBirthDate().toString()) .claim("phone", user.getPhone()) .claim("profileImage", user.getProfileImage()) .claim("role", user.getRole()) + .claim("categoryList", userCategoryList) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + AccessTokenValidTime)) .signWith(SignatureAlgorithm.HS512, SecretKey) @@ -106,23 +109,4 @@ public static boolean validateToken(String token) { } 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 5096b54c..554cdf8d 100644 --- a/src/main/java/com/example/demo/config/security/SecurityConfig.java +++ b/src/main/java/com/example/demo/config/security/SecurityConfig.java @@ -76,7 +76,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws // 11. 인증, 권한 필터 설정 httpSecurity.authorizeRequests( - authorize -> authorize + authorize -> authorize.antMatchers("/profiles", "/users/passwordcheck").authenticated() .antMatchers("/admin/**").access("hasRole('ADMIN')") .anyRequest().permitAll() ); diff --git a/src/main/java/com/example/demo/interest/Interest.java b/src/main/java/com/example/demo/interest/Interest.java index 504be61f..8e64541e 100644 --- a/src/main/java/com/example/demo/interest/Interest.java +++ b/src/main/java/com/example/demo/interest/Interest.java @@ -12,7 +12,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @Table(name = "interests") -public class Interest extends BaseTime { +public class Interest { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; diff --git a/src/main/java/com/example/demo/mentoring/MentorPostResponse.java b/src/main/java/com/example/demo/mentoring/MentorPostResponse.java index 405a019b..8458db6d 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostResponse.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostResponse.java @@ -9,6 +9,7 @@ import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -130,7 +131,7 @@ public static class MenteeDTO{ private String name; private String country; private Role role; - private int age; + private LocalDate birthDate; private ContactStateEnum state; private List interests; @@ -140,7 +141,7 @@ public MenteeDTO(NotConnectedRegisterUser notConnectedRegisterUser, List userInterest.getInterest().getCategory()) diff --git a/src/main/java/com/example/demo/mentoring/MentorPostRestController.java b/src/main/java/com/example/demo/mentoring/MentorPostRestController.java index 17fd6dc2..db11c5e5 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostRestController.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostRestController.java @@ -2,6 +2,7 @@ import com.example.demo.config.auth.CustomUserDetails; import com.example.demo.config.utils.ApiResponseBuilder; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,43 +19,46 @@ public class MentorPostRestController { private final MentorPostService mentorPostService; @PostMapping(value = "/mentorings/post") + @Operation(summary = "mentorpost 생성") public ResponseEntity createMentorPost(@RequestBody @Valid MentorPostRequest.CreateDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { mentorPostService.createMentorPost(requestDTO, userDetails.getUser()); return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } @GetMapping("/mentorings/post") + @Operation(summary = "mentorpost 가져오기", description = "category, search로 필터링, pagination 적용") public ResponseEntity getMentorPost( - @RequestParam(value = "category", required = false) MentorPostCategoryEnum category, + @RequestParam(value = "category", defaultValue = "NULL") String 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}") + @Operation(summary = "mentorpost 개별 페이지 불러오기") public ResponseEntity getMentorPostId(@PathVariable int id) { MentorPostResponse.MentorPostDTO responseDTO = mentorPostService.findMentorPost(id); return ResponseEntity.ok(ApiResponseBuilder.success(responseDTO)); } @PutMapping(value = "/mentorings/post/{id}") + @Operation(summary = "mentorpost 수정 요청") public ResponseEntity updateMentorPost(@PathVariable int id, @RequestBody @Valid MentorPostRequest.CreateDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { mentorPostService.updateMentorPost(requestDTO, id, userDetails.getUser()); return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); } @DeleteMapping(value = "/mentorings/post/{id}") + @Operation(summary = "mentorpost 삭제 요청") 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") + @Operation(summary = "mentorpost 만료 요청") public ResponseEntity changeMentorPostStatus(@PathVariable int id,@RequestBody @Valid MentorPostRequest.StateDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { 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 be3f9c45..a86aac5a 100644 --- a/src/main/java/com/example/demo/mentoring/MentorPostService.java +++ b/src/main/java/com/example/demo/mentoring/MentorPostService.java @@ -17,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; @Transactional @@ -51,45 +52,18 @@ public void createMentorPost(MentorPostRequest.CreateDTO createDTO, User writer) /* 1. mentorPostList를 조회 2. 각 List당 writer별 writerInterests를 조회 3. MentorPostDTO 생성*/ - public List findAllMentorPost(MentorPostCategoryEnum searchCategory, String keyword, int page) { + public List findAllMentorPost(String search, 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("검색 분류가 잘못되었습니다."); - } + MentorSearchCategory mentorSearchCategory = MentorSearchCategory.valueOf(search.toUpperCase(Locale.ROOT)); - if(pageContent.getTotalPages() == 0){ - throw new Exception404("해당 글들이 존재하지 않습니다"); - } + Page result = mentorSearchCategory.execute(keyword, pageable, mentorPostJPARepository); + List mentorPostAllDTOS = result.stream().map(mentorPost -> { + List writerInterests = userInterestJPARepository.findAllById(mentorPost.getWriter().getId()); + return new MentorPostResponse.MentorPostAllDTO(mentorPost, writerInterests); + }).collect(Collectors.toList()); - //List mentorPostList = mentorPostJPARepostiory.findAll(); - List mentorPostDTOList = pageContent.getContent().stream().map( - mentorPost -> { - List writerInterests = userInterestJPARepository.findAllById(mentorPost.getWriter().getId()); - return new MentorPostResponse.MentorPostAllDTO(mentorPost,writerInterests); - } - ).collect(Collectors.toList()); - return mentorPostDTOList; + return mentorPostAllDTOS; } public MentorPostResponse.MentorPostDTO findMentorPost(int id){ @@ -149,10 +123,6 @@ public List findAllMentorPostW List mentorPostDTOList = pageContent.stream().map( mentorPost -> { List writerInterests = userInterestJPARepository.findAllById(mentorPost.getWriter().getId()); - if(writerInterests.isEmpty()){ - throw new Exception404("해당 카테고리는 존재하지 않습니다"); - } - return new MentorPostResponse.MentorPostAllWithTimeStampDTO(mentorPost,writerInterests); } ).collect(Collectors.toList()); diff --git a/src/main/java/com/example/demo/mentoring/MentorSearchCategory.java b/src/main/java/com/example/demo/mentoring/MentorSearchCategory.java new file mode 100644 index 00000000..836db593 --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/MentorSearchCategory.java @@ -0,0 +1,23 @@ +package com.example.demo.mentoring; + +import lombok.Getter; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +@Getter +public enum MentorSearchCategory { + NULL((keyword, pageable, repo) -> repo.findAll(pageable)), + TITLE((keyword, pageable, repo) -> repo.findAllByTitleKeyword("%" + keyword + "%", pageable)), + WRITER((keyword, pageable, repo) -> repo.findAllByWriterKeyword("%" + keyword + "%", pageable)), + INTEREST((keyword, pageable, repo) -> repo.findAllByInterestKeyword("%" + keyword + "%", pageable)); + + private final TriFunction> function; + + MentorSearchCategory(TriFunction> function) { + this.function = function; + } + + public Page execute(String keyword, Pageable pageable, MentorPostJPARepostiory repository) { + return function.apply(keyword, pageable, repository); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/mentoring/TriFunction.java b/src/main/java/com/example/demo/mentoring/TriFunction.java new file mode 100644 index 00000000..d60fcb2a --- /dev/null +++ b/src/main/java/com/example/demo/mentoring/TriFunction.java @@ -0,0 +1,6 @@ +package com.example.demo.mentoring; + +@FunctionalInterface +public interface TriFunction { + R apply(T t, U u, V v); +} 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 ff83058c..f996918d 100644 --- a/src/main/java/com/example/demo/mentoring/contact/ContactResponse.java +++ b/src/main/java/com/example/demo/mentoring/contact/ContactResponse.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; @@ -67,7 +68,7 @@ public static class ContactMentorDTO { private String profileImage; private String name; private String country; - private int age; + private LocalDate birthDate; private Role role; private List favorites; @@ -76,7 +77,7 @@ public ContactMentorDTO(User mentor, List userInterests) { this.profileImage = mentor.getProfileImage(); this.name = mentor.getFirstName() + " " + mentor.getLastName(); this.country = mentor.getCountry(); - this.age = mentor.getAge(); + this.birthDate = mentor.getBirthDate(); this.role = mentor.getRole(); this.favorites = userInterests.stream() .map(userInterest -> userInterest.getInterest().getCategory()) @@ -89,7 +90,7 @@ public static class ContactMenteeDTO { private String profileImage; private String name; private String country; - private int age; + private LocalDate birthDate; private Role role; private ContactStateEnum state; private List favorites; // 고민할 부분 : 유저의 favorite List 를 어떻게 가져올 것 인가? @@ -106,7 +107,7 @@ public ContactMenteeDTO(NotConnectedRegisterUser notConnectedRegisterUser, List< this.profileImage = notConnectedRegisterUser.getMenteeUser().getProfileImage(); this.name = notConnectedRegisterUser.getMenteeUser().getFirstName() + " " + notConnectedRegisterUser.getMenteeUser().getLastName(); this.country = notConnectedRegisterUser.getMenteeUser().getCountry(); - this.age = notConnectedRegisterUser.getMenteeUser().getAge(); + this.birthDate = notConnectedRegisterUser.getMenteeUser().getBirthDate(); this.role = notConnectedRegisterUser.getMenteeUser().getRole(); this.state = notConnectedRegisterUser.getState(); this.favorites = userInterests.stream() 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 e76c4b98..32ece897 100644 --- a/src/main/java/com/example/demo/mentoring/contact/NotConnectedRegisterUser.java +++ b/src/main/java/com/example/demo/mentoring/contact/NotConnectedRegisterUser.java @@ -19,10 +19,10 @@ @Where(clause = "deleted_at IS NULL") @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") - }, +// 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"}) }) diff --git a/src/main/java/com/example/demo/mentoring/done/DoneResponse.java b/src/main/java/com/example/demo/mentoring/done/DoneResponse.java index 90f9df96..8e5e6bca 100644 --- a/src/main/java/com/example/demo/mentoring/done/DoneResponse.java +++ b/src/main/java/com/example/demo/mentoring/done/DoneResponse.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; @@ -33,7 +34,7 @@ public static class DoneMentorDTO { private String profileImage; private String name; private String country; - private int age; + private LocalDate birthDate; private Role role; private List favorites; @@ -42,7 +43,7 @@ public DoneMentorDTO(User mentor, List userInterests) { this.profileImage = mentor.getProfileImage(); this.name = mentor.getFirstName() + " " + mentor.getLastName(); this.country = mentor.getCountry(); - this.age = mentor.getAge(); + this.birthDate = mentor.getBirthDate(); this.role = mentor.getRole(); this.favorites = userInterests.stream() .map(userInterest -> userInterest.getInterest().getCategory()) @@ -55,15 +56,16 @@ public static class DoneMenteeDTO { private String profileImage; private String name; private String country; - private int age; + private LocalDate birthDate; 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.birthDate = connectedUser.getMenteeUser().getBirthDate(); this.role = connectedUser.getMenteeUser().getRole(); this.favorites = userInterests.stream() .map(userInterest -> userInterest.getInterest().getCategory()) diff --git a/src/main/java/com/example/demo/refreshToken/TokenController.java b/src/main/java/com/example/demo/refreshToken/TokenController.java index c05388e7..b690fa12 100644 --- a/src/main/java/com/example/demo/refreshToken/TokenController.java +++ b/src/main/java/com/example/demo/refreshToken/TokenController.java @@ -7,10 +7,14 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.Cookie; +import java.io.UnsupportedEncodingException; + @Api(tags = "Token API") @RequiredArgsConstructor @RestController @@ -20,7 +24,7 @@ public class TokenController { @Operation(summary = "Access Token 재발급", description = "Refresh Token을 이용하여 Access Token 재발급") @GetMapping(value = "/users/refresh") - public ResponseEntity refreshToken(@RequestHeader(value = "Authorization") String refreshToken) { + public ResponseEntity refreshToken(@CookieValue(value = "RefreshToken") String refreshToken) throws UnsupportedEncodingException { 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/TokenService.java b/src/main/java/com/example/demo/refreshToken/TokenService.java index d2ece77b..9e5c7b1f 100644 --- a/src/main/java/com/example/demo/refreshToken/TokenService.java +++ b/src/main/java/com/example/demo/refreshToken/TokenService.java @@ -1,25 +1,42 @@ 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.errors.exception.Exception404; import com.example.demo.config.jwt.JWTTokenProvider; +import com.example.demo.user.User; +import com.example.demo.user.UserJPARepository; +import com.example.demo.user.userInterest.UserInterestJPARepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.List; +import java.util.stream.Collectors; + @Transactional @RequiredArgsConstructor @Service public class TokenService { + private final UserJPARepository userJPARepository; + private final UserInterestJPARepository userInterestJPARepository; private final RefreshTokenJPARepository refreshTokenJPARepository; private final JWTTokenProvider jwtTokenProvider; - public String reissueAccessToken(String jwtRefreshToken) { - if (jwtRefreshToken == null || !(jwtRefreshToken.startsWith("Bearer "))) { + public String reissueAccessToken(String jwtRefreshToken) throws UnsupportedEncodingException { + + String decodedJwtRefreshToken = URLDecoder.decode(jwtRefreshToken, "utf-8"); + + if (decodedJwtRefreshToken == null || !(decodedJwtRefreshToken.startsWith("Bearer "))) { throw new Exception401("유효하지 않은 토큰입니다.1"); } - String extractedJwtRefreshToken = jwtRefreshToken.replace(JWTTokenProvider.Token_Prefix, ""); + + String extractedJwtRefreshToken = decodedJwtRefreshToken.replace(JWTTokenProvider.Token_Prefix, ""); if (jwtTokenProvider.validateToken(extractedJwtRefreshToken)) { int id = Integer.valueOf(jwtTokenProvider.decodeJwtToken(extractedJwtRefreshToken).get("id").toString()); RefreshToken refreshTokenInfo = refreshTokenJPARepository.findByUserId(id) @@ -29,7 +46,13 @@ public String reissueAccessToken(String jwtRefreshToken) { throw new Exception401("유효하지 않은 토큰입니다.3"); } - String accessToken = JWTTokenProvider.createAccessToken(refreshTokenInfo.getUser()); + User user = userJPARepository.findById(refreshTokenInfo.getUser().getId()) + .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + List userCategoryList = userInterestJPARepository.findAllById(user.getId()).stream() + .map(interest -> interest.getInterest().getCategory()) + .collect(Collectors.toList()); + + String accessToken = JWTTokenProvider.createAccessToken(user, userCategoryList); 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 a2605dd9..f2476f8a 100644 --- a/src/main/java/com/example/demo/user/User.java +++ b/src/main/java/com/example/demo/user/User.java @@ -1,13 +1,20 @@ package com.example.demo.user; import com.example.demo.config.utils.BaseTime; +import com.example.demo.mentoring.MentorPostStateConverter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.Serializers; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; +import java.time.LocalDate; import java.time.LocalDateTime; @Getter @@ -38,7 +45,10 @@ public class User extends BaseTime { private String introduction; @Column(nullable = false) - private int age; + @DateTimeFormat(pattern = "yyyy-MM-dd") + @JsonSerialize(using = LocalDateSerializer.class) + @JsonDeserialize(using = LocalDateDeserializer.class) + private LocalDate birthDate; @Column(nullable = false) private String phone; @@ -47,33 +57,30 @@ public class User extends BaseTime { private String profileImage; @Column(nullable = false) - @Enumerated(value = EnumType.STRING) + @Convert(converter = RoleConverter.class) private Role role; @Builder - public User(String firstName, String lastName, String email, String password, String country, String introduction, int age, String phone, String profileImage, Role role) { - this.email = email; + public User(String firstName, String lastName, String email, String password, String country, String introduction, LocalDate birthDate, String phone, String profileImage, Role role) { this.firstName = firstName; this.lastName = lastName; + this.email = email; this.password = password; this.country = country; this.introduction = introduction; - this.age = age; + this.birthDate = birthDate; this.phone = phone; this.profileImage = profileImage; this.role = role; } - public void updateProfile(User user) { - this.email = user.getEmail(); + public User updateProfile(User user) { this.firstName = user.getFirstName(); this.lastName = user.getLastName(); + this.email = user.getEmail(); this.password = user.getPassword(); this.country = user.getCountry(); - this.introduction = user.getIntroduction(); - this.age = user.getAge(); - this.phone = user.getPhone(); - this.profileImage = user.getProfileImage(); this.role = user.getRole(); + return user; } } diff --git a/src/main/java/com/example/demo/user/UserRequest.java b/src/main/java/com/example/demo/user/UserRequest.java index 15125f0b..aeea3eb6 100644 --- a/src/main/java/com/example/demo/user/UserRequest.java +++ b/src/main/java/com/example/demo/user/UserRequest.java @@ -43,11 +43,12 @@ public static class SignUpDTO { private String introduction; @NotNull(message = "생년월일을 입력해주세요.") - private LocalDate birthdate; + private LocalDate birthDate; @NotNull(message = "연락처를 입력해주세요.") + @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$") private String phone; - + // 프로필 이미지의 경우, 추후 변경 예정 private String profileImage; @@ -55,21 +56,23 @@ public static class SignUpDTO { private Role role; @NotNull(message = "관심 분야를 선택해주세요.") + @Size(min = 1, max = 3) private List categoryList; -// public User toEntity() { -// return User.builder() -// .firstName(firstName) -// .lastName(lastName) -// .email(email) -// .password(password) -// .country(country) -// .introduction(introduction) -// .age(age) -// .profileImage(profileImage) -// .role(role) -// .build(); -// } + public User toEntity() { + return User.builder() + .firstName(firstName) + .lastName(lastName) + .email(email) + .password(password) + .country(country) + .introduction(introduction) + .birthDate(birthDate) + .phone(phone) + .profileImage(profileImage) + .role(role) + .build(); + } } @Getter @@ -85,6 +88,15 @@ public static class LoginDTO { private String password; } + @Getter + @Setter + public static class PasswordCheckDTO { + @NotNull + @Size(min = 8, max = 16, message = "8~16자 이내로 입력해주세요.") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@#$%^&+=!~`<>,./?;:'\"\\[\\]{}\\\\()|_-])\\S*$", message = "영문 대/소문자, 숫자, 특수문자를 포함해주세요.") + private String password; + } + @Getter @Setter public static class ProfileUpdateDTO { @@ -94,10 +106,6 @@ public static class ProfileUpdateDTO { @NotNull(message = "성을 입력해주세요.") private String lastName; - @NotNull - @Pattern(regexp = "^[\\w._%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$", message = "이메일을 올바르게 입력해주세요.") - private String email; - @NotNull @Size(min = 8, max = 16, message = "8~16자 이내로 입력해주세요.") @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@#$%^&+=!~`<>,./?;:'\"\\[\\]{}\\\\()|_-])\\S*$", message = "영문 대/소문자, 숫자, 특수문자를 포함해주세요.") @@ -108,10 +116,11 @@ public static class ProfileUpdateDTO { private String introduction; - @NotNull(message = "나이를 입력해주세요.") - private int age; + @NotNull(message = "생년월일을 입력해주세요.") + private LocalDate birthDate; @NotNull(message = "연락처를 입력해주세요.") + @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$") private String phone; // 프로필 이미지의 경우, 추후 변경 예정 @@ -121,6 +130,21 @@ public static class ProfileUpdateDTO { private Role role; @NotNull(message = "관심 분야를 선택해주세요.") + @Size(min = 1, max = 3) private List categoryList; + + public User toEntity() { + return User.builder() + .firstName(firstName) + .lastName(lastName) + .password(password) + .country(country) + .introduction(introduction) + .birthDate(birthDate) + .phone(phone) + .profileImage(profileImage) + .role(role) + .build(); + } } } diff --git a/src/main/java/com/example/demo/user/UserResponse.java b/src/main/java/com/example/demo/user/UserResponse.java index 6abc02f5..98dd16dc 100644 --- a/src/main/java/com/example/demo/user/UserResponse.java +++ b/src/main/java/com/example/demo/user/UserResponse.java @@ -1,9 +1,15 @@ package com.example.demo.user; +import com.example.demo.interest.Interest; import com.example.demo.refreshToken.TokenResponse; +import com.example.demo.user.userInterest.UserInterest; import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; + public class UserResponse { @Getter @Setter @@ -11,8 +17,8 @@ public static class LoginDTO { private UserDetailDTO userDetailDTO; private JWTToken jwtToken; - public LoginDTO(User user, TokenResponse.TokenDTO token) { - this.userDetailDTO = new UserDetailDTO(user); + public LoginDTO(User user, List userCategoryList, TokenResponse.TokenDTO token) { + this.userDetailDTO = new UserDetailDTO(user, userCategoryList); this.jwtToken = new JWTToken(token); } @@ -24,17 +30,21 @@ public class UserDetailDTO { private String firstName; private String lastName; private String country; + private LocalDate birthDate; private String profileImage; private Role role; + private List categorylist; - public UserDetailDTO(User user) { + public UserDetailDTO(User user, List userCategoryList) { this.id = user.getId(); this.email = user.getEmail(); this.firstName = user.getFirstName(); this.lastName = user.getLastName(); this.country = user.getCountry(); + this.birthDate = user.getBirthDate(); this.profileImage = user.getProfileImage(); this.role = user.getRole(); + this.categorylist = userCategoryList; } } @@ -53,27 +63,47 @@ public JWTToken(TokenResponse.TokenDTO token) { @Getter @Setter - public static class ProfileDTO { + public static class SimpleProfileDTO { private int id; private String profileImage; private String firstName; private String lastName; - private String introduction; - private String country; - private String role; - private String phone; private String email; - public ProfileDTO(User user) { + public SimpleProfileDTO(User user) { this.id = user.getId(); this.profileImage = user.getProfileImage(); this.firstName = user.getFirstName(); this.lastName = user.getLastName(); - this.introduction = user.getIntroduction(); - this.country = user.getCountry(); - this.role = user.getRole().toString(); - this.phone = user.getPhone(); this.email = user.getEmail(); } } + + @Getter + @Setter + public static class ProfileDTO { + private int id; + private String email; + private String firstName; + private String lastName; + private String country; + private String introduction; + private LocalDate birthDate; + private String profileImage; + private Role role; + private List categorylist; + + public ProfileDTO(User user, List userCategoryList) { + this.id = user.getId(); + this.email = user.getEmail(); + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + this.country = user.getCountry(); + this.introduction = user.getIntroduction(); + this.birthDate = user.getBirthDate(); + this.profileImage = user.getProfileImage(); + this.role = user.getRole(); + this.categorylist = userCategoryList; + } + } } diff --git a/src/main/java/com/example/demo/user/UserRestController.java b/src/main/java/com/example/demo/user/UserRestController.java index eb80f165..243e3539 100644 --- a/src/main/java/com/example/demo/user/UserRestController.java +++ b/src/main/java/com/example/demo/user/UserRestController.java @@ -4,6 +4,7 @@ import com.example.demo.config.jwt.JWTTokenProvider; import com.example.demo.config.utils.ApiResponseBuilder; import io.swagger.annotations.Api; +import io.swagger.models.Response; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -54,23 +55,37 @@ public ResponseEntity login(@RequestBody @Valid UserRequest.LoginDTO requestD cookie.setHttpOnly(true); cookie.setSecure(true); cookie.setPath("/"); - cookie.setMaxAge(60 * 5); + cookie.setMaxAge(60 * 60 * 24 * 7); httpServletResponse.addCookie(cookie); return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.success(responseDTO.getUserDetailDTO())); } + @Operation(summary = "간단한 프로필 정보", description = "간단한 프로필 정보") + @GetMapping(value = "/profiles/simple") + public ResponseEntity simpleProfile(@AuthenticationPrincipal CustomUserDetails userDetails) { + UserResponse.SimpleProfileDTO responseDTO = userService.findSimpleProfile(userDetails); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.success(responseDTO)); + } + @Operation(summary = "마이 페이지 프로필 확인", description = "마이 페이지에서 프로필 확인") @GetMapping(value = {"/profiles", "/profiles/{id}"}) public ResponseEntity profile(@PathVariable(required = false) Integer id, @AuthenticationPrincipal CustomUserDetails userDetails) { - UserResponse.ProfileDTO responseDTO = userService.findProfile(id, userDetails.getUser()); + UserResponse.ProfileDTO responseDTO = userService.findProfile(id, userDetails); return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.success(responseDTO)); } - + + @Operation(summary = "마이 페이지 프로필 수정 전 본인 확인", description = "마이 페이지 프로필 수정 전 본인 확인") + @PostMapping(value = "/users/passwordcheck") + public ResponseEntity passwordCheck(@RequestBody @Valid UserRequest.PasswordCheckDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails) { + userService.passwordCheck(requestDTO, userDetails); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseBuilder.successWithNoContent()); + } + @Operation(summary = "마이 페이지 프로필 수정", description = "마이 페이지에서 프로필 수정") @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); + UserResponse.ProfileDTO responseDTO = userService.updateProfile(userDetails, 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 565926ed..75aee2f6 100644 --- a/src/main/java/com/example/demo/user/UserService.java +++ b/src/main/java/com/example/demo/user/UserService.java @@ -1,5 +1,6 @@ package com.example.demo.user; +import com.example.demo.config.auth.CustomUserDetails; import com.example.demo.config.errors.exception.Exception400; import com.example.demo.config.errors.exception.Exception404; import com.example.demo.interest.Interest; @@ -10,6 +11,7 @@ import com.example.demo.refreshToken.TokenResponse; import com.example.demo.user.userInterest.UserInterest; import com.example.demo.user.userInterest.UserInterestJPARepository; +import jdk.jfr.Category; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -20,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Transactional @RequiredArgsConstructor @@ -41,23 +44,8 @@ public void emailCheck(UserRequest.EmailCheckDTO requestDTO) { @Transactional public void signup(UserRequest.SignUpDTO requestDTO) { - // requestDTO 의 Birthdate -> age 로 변경해주는 코드 - // 나이 계산하기 - LocalDate currentDate = LocalDate.now(); // 현재 날짜 가져오기 - int age = Period.between(requestDTO.getBirthdate(), currentDate).getYears(); // 현재 날짜와 생일을 비교해서 나이 계산 - - User user = User.builder().firstName(requestDTO.getFirstName()) - .lastName(requestDTO.getLastName()) - .email(requestDTO.getEmail()) - .password(passwordEncoder.encode(requestDTO.getPassword())) - .country(requestDTO.getCountry()) - .introduction(requestDTO.getIntroduction()) - .age(age) - .phone(requestDTO.getPhone()) - .profileImage(requestDTO.getProfileImage()) - .role(requestDTO.getRole()) - .build(); - userJPARepository.save(user); + requestDTO.setPassword(passwordEncoder.encode(requestDTO.getPassword())); + User user = userJPARepository.save(requestDTO.toEntity()); List userInterestList = new ArrayList<>(); List categoryList = requestDTO.getCategoryList(); @@ -79,7 +67,11 @@ public UserResponse.LoginDTO login(UserRequest.LoginDTO requestDTO) { throw new Exception400(null, "잘못된 비밀번호입니다."); } - TokenResponse.TokenDTO token = JWTTokenProvider.createToken(user); + List userCategoryList = userInterestJPARepository.findAllById(user.getId()).stream() + .map(interest -> interest.getInterest().getCategory()) + .collect(Collectors.toList()); + + TokenResponse.TokenDTO token = JWTTokenProvider.createToken(user, userCategoryList); Optional refreshTokenInfo = refreshTokenJPARepository.findByUser(user); if (refreshTokenInfo.isPresent()) { @@ -89,52 +81,67 @@ public UserResponse.LoginDTO login(UserRequest.LoginDTO requestDTO) { refreshTokenJPARepository.save(newRefreshToken); } - return new UserResponse.LoginDTO(user, token); + return new UserResponse.LoginDTO(user, userCategoryList, token); + } + + public UserResponse.SimpleProfileDTO findSimpleProfile(CustomUserDetails userDetails) { + User user = userJPARepository.findById(userDetails.getUser().getId()) + .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + return new UserResponse.SimpleProfileDTO(user); } - public UserResponse.ProfileDTO findProfile(Integer id, User sessionUser) { + public UserResponse.ProfileDTO findProfile(Integer id, CustomUserDetails userDetails) { User user; + List userCategoryList = new ArrayList<>(); + if (id == null) { - user = userJPARepository.findById(sessionUser.getId()) + user = userJPARepository.findById(userDetails.getUser().getId()) .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); } else { user = userJPARepository.findById(id) .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + userCategoryList = userInterestJPARepository.findAllById(user.getId()).stream() + .map(interest -> interest.getInterest().getCategory()) + .collect(Collectors.toList()); + } + return new UserResponse.ProfileDTO(user, userCategoryList); + } + + public void passwordCheck(UserRequest.PasswordCheckDTO requestDTO, CustomUserDetails userDetails) { + User user = userJPARepository.findById(userDetails.getUser().getId()) + .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + + if (!passwordEncoder.matches(requestDTO.getPassword(), user.getPassword())) { + throw new Exception400(null, "잘못된 비밀번호입니다."); } - return new UserResponse.ProfileDTO(user); } @Transactional - public UserResponse.ProfileDTO updateProfile(int id, UserRequest.ProfileUpdateDTO requestDTO) { - User user = userJPARepository.findById(id) + public UserResponse.ProfileDTO updateProfile(CustomUserDetails userDetails, UserRequest.ProfileUpdateDTO requestDTO) { + User user = userJPARepository.findById(userDetails.getUser().getId()) .orElseThrow(() -> new Exception404("해당 사용자가 존재하지 않습니다.")); + user = user.updateProfile(requestDTO.toEntity()); + + List userCategoryList = userInterestJPARepository.findAllById(user.getId()).stream() + .map(interest -> interest.getInterest().getCategory()) + .collect(Collectors.toList()); + List newUserCategoryList = requestDTO.getCategoryList(); + + for (String newUserCategory : newUserCategoryList) { + if (!userCategoryList.contains(newUserCategory)) { + Interest interest = interestJPARepository.findByCategory(newUserCategory) + .orElseThrow(() -> new Exception400(null, "해당 관심사가 존재하지 않습니다.")); + UserInterest newUserInterest = UserInterest.builder().user(user).interest(interest).build(); + userInterestJPARepository.save(newUserInterest); + } + } - User updateUser = User.builder() - .firstName(requestDTO.getFirstName()) - .lastName(requestDTO.getLastName()) - .email(requestDTO.getEmail()) - .password(passwordEncoder.encode(requestDTO.getPassword())) - .country(requestDTO.getCountry()) - .introduction(requestDTO.getIntroduction()) - .age(requestDTO.getAge()) - .phone(requestDTO.getPhone()) - .profileImage(requestDTO.getProfileImage()) - .role(requestDTO.getRole()) - .build(); - user.updateProfile(updateUser); - - userInterestJPARepository.deleteAllByUserId(id); - - List updateUserInterestList = new ArrayList<>(); - List categoryList = requestDTO.getCategoryList(); - for (String category : categoryList) { - Interest interest = interestJPARepository.findByCategory(category) - .orElseThrow(() -> new Exception400(null,"해당 관심사가 존재하지 않습니다.")); - UserInterest updateUserInterest = UserInterest.builder().user(user).interest(interest).build(); - updateUserInterestList.add(updateUserInterest); + for (String userCategory: userCategoryList) { + if (!newUserCategoryList.contains(userCategory)) { + userInterestJPARepository.deleteByUserAndInterest(user.getId(), userCategory); + } } - userInterestJPARepository.saveAll(updateUserInterestList); - return new UserResponse.ProfileDTO(user); + return new UserResponse.ProfileDTO(user, userCategoryList); } } diff --git a/src/main/java/com/example/demo/user/userInterest/UserInterestJPARepository.java b/src/main/java/com/example/demo/user/userInterest/UserInterestJPARepository.java index c53fb831..9bf8d8a9 100644 --- a/src/main/java/com/example/demo/user/userInterest/UserInterestJPARepository.java +++ b/src/main/java/com/example/demo/user/userInterest/UserInterestJPARepository.java @@ -3,14 +3,16 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.security.core.parameters.P; import java.util.List; +import java.util.Optional; public interface UserInterestJPARepository extends JpaRepository { - @Query("select u from UserInterest u join fetch u.interest i where u.user.id = :id") + @Query("select u from UserInterest u where u.user.id = :id") List findAllById(@Param("id") int id); - @Query("select ui from UserInterest ui where ui.user.id = :id") - void deleteAllByUserId(@Param("id") int id); + @Query("select ui from UserInterest ui where ui.user.id = :user_id and ui.interest.category = :category") + void deleteByUserAndInterest(@Param("user_id") int user_id, @Param("category") String category); } diff --git a/src/main/java/com/example/demo/video/VideoJPARepository.java b/src/main/java/com/example/demo/video/VideoJPARepository.java index f7a56be4..a80ab8e4 100644 --- a/src/main/java/com/example/demo/video/VideoJPARepository.java +++ b/src/main/java/com/example/demo/video/VideoJPARepository.java @@ -29,4 +29,7 @@ public interface VideoJPARepository extends JpaRepository { @Query(value = "select v from Video v inner join VideoInterest vi on v.id = vi.video.id where vi.interest.id = :categoryId") Page