From ee390c27ac1acac72fa8e1748a9c08c8c6161a01 Mon Sep 17 00:00:00 2001 From: gengminy Date: Sat, 1 Oct 2022 23:28:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:hammer:=20fix(jwt):=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/constant/Role.java | 25 +++++++++++++++ .../member/dto/MemberCommonResponseDto.java | 5 +++ .../member/dto/MemberDetailResponseDto.java | 4 +++ .../domain/member/entity/Member.java | 6 ++++ .../domain/recommend/RecommendController.java | 17 +++++++++- .../sms/SmsCertificationServiceImpl.java | 2 +- .../global/config/security/MemberAdapter.java | 12 +++++-- .../security/UserDetailServiceImpl.java | 11 ++----- .../global/config/security/dto/JwtDTO.java | 12 +++++-- .../security/jwt/JwtAuthenticationFilter.java | 8 ++--- .../config/security/jwt/JwtTokenProvider.java | 31 ++++++++++++++----- .../naechinso/global/error/ErrorResponse.java | 2 +- 12 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/tikitaka/naechinso/domain/member/constant/Role.java diff --git a/src/main/java/com/tikitaka/naechinso/domain/member/constant/Role.java b/src/main/java/com/tikitaka/naechinso/domain/member/constant/Role.java new file mode 100644 index 0000000..9f471ef --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/domain/member/constant/Role.java @@ -0,0 +1,25 @@ +package com.tikitaka.naechinso.domain.member.constant; + +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Role { + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private final String detail; + + //Enum Validation 을 위한 코드, enum 에 속하지 않으면 null 리턴 + @JsonCreator + public static Role fromRole(String val){ + for(Role role : Role.values()){ + if(role.name().equals(val)){ + return role; + } + } + return null; + } +} diff --git a/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberCommonResponseDto.java b/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberCommonResponseDto.java index 7a2281b..e5b74f2 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberCommonResponseDto.java +++ b/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberCommonResponseDto.java @@ -1,6 +1,7 @@ package com.tikitaka.naechinso.domain.member.dto; import com.tikitaka.naechinso.domain.member.constant.Gender; +import com.tikitaka.naechinso.domain.member.constant.Role; import com.tikitaka.naechinso.domain.member.entity.Member; import lombok.*; @@ -14,8 +15,11 @@ @Builder @ToString public class MemberCommonResponseDto { + private String phone; + private Role role; + private String name; private Gender gender; @@ -25,6 +29,7 @@ public class MemberCommonResponseDto { public static MemberCommonResponseDto of(Member member) { MemberCommonResponseDto res = MemberCommonResponseDto.builder() .phone(member.getPhone()) + .role(member.getRole()) .name(member.getName()) .gender(member.getGender()) .age(member.getAge()) diff --git a/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberDetailResponseDto.java b/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberDetailResponseDto.java index 2a0b5ea..ed2815f 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberDetailResponseDto.java +++ b/src/main/java/com/tikitaka/naechinso/domain/member/dto/MemberDetailResponseDto.java @@ -1,6 +1,7 @@ package com.tikitaka.naechinso.domain.member.dto; import com.tikitaka.naechinso.domain.member.constant.Gender; +import com.tikitaka.naechinso.domain.member.constant.Role; import com.tikitaka.naechinso.domain.member.entity.Member; import com.tikitaka.naechinso.domain.member.entity.MemberDetail; import com.tikitaka.naechinso.global.error.ErrorCode; @@ -26,6 +27,8 @@ public class MemberDetailResponseDto { //추천인 정보 private String phone; + private Role role; + private String name; private Gender gender; @@ -84,6 +87,7 @@ private static MemberDetailResponseDto buildDetail(MemberDetail detail) { //멤버 정보가 연결되어 있으면 가져옴 if (member != null) { dtoBuilder.phone(member.getPhone()) + .role(member.getRole()) .name(member.getName()) .gender(member.getGender()) .age(member.getAge()); diff --git a/src/main/java/com/tikitaka/naechinso/domain/member/entity/Member.java b/src/main/java/com/tikitaka/naechinso/domain/member/entity/Member.java index e92cabc..4d2fe06 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/member/entity/Member.java +++ b/src/main/java/com/tikitaka/naechinso/domain/member/entity/Member.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.tikitaka.naechinso.domain.member.constant.Gender; +import com.tikitaka.naechinso.domain.member.constant.Role; import com.tikitaka.naechinso.domain.recommend.entity.Recommend; import com.tikitaka.naechinso.global.config.entity.BaseEntity; import lombok.*; @@ -32,6 +33,11 @@ public class Member extends BaseEntity { @Column(name = "mem_phone") private String phone; + @Column(name = "mem_role", columnDefinition = "Text default 'ROLE_USER'") + @Builder.Default + @Enumerated(EnumType.STRING) + private Role role = Role.USER; + @Column(name = "mem_name") private String name; diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java index 39645b9..ffc1049 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java @@ -74,7 +74,22 @@ public CommonApiResponse createRecommendRequest( // //제일 아래에 있어야함 // @GetMapping("/{uuid}") -// @ApiOperation(value = "추천 요청 uuid 를 가진 추천사 엔티티를 가져온다") +// @ApiOperation(value = "추천 요청받은 uuid 를 가진 추천사 정보를 가져온다") +// public CommonApiResponse getRecommendByUuid( +// HttpServletRequest request, +// @PathVariable("uuid") String uuid, +// @Valid @RequestBody RecommendRequestDTO dto) +// { +// String registerToken = request.getHeader("Authorization"); +// String phone = parseRegisterToken(registerToken); +// +//// RecommendDTO recommendDTO = recommendService.findAllRecommendRequestsByUuid(uuid); +// return CommonApiResponse.of(recommendDTO); +// } + + // //제일 아래에 있어야함 +// @PatchMapping("/{uuid}") +// @ApiOperation(value = "요청받은 uuid 추천사에 추천인을 등록한다") // public CommonApiResponse getRecommendByUuid( // HttpServletRequest request, // @PathVariable("uuid") String uuid, diff --git a/src/main/java/com/tikitaka/naechinso/domain/sms/SmsCertificationServiceImpl.java b/src/main/java/com/tikitaka/naechinso/domain/sms/SmsCertificationServiceImpl.java index 330009a..b2dbdf3 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/sms/SmsCertificationServiceImpl.java +++ b/src/main/java/com/tikitaka/naechinso/domain/sms/SmsCertificationServiceImpl.java @@ -137,7 +137,7 @@ public SmsCertificationSuccessResponseDTO verifyCode(SmsCertificationRequestDTO //이미 가입한 회원이면 //인증한 휴대폰 번호로 로그인 후 토큰 생성 TokenResponseDTO tokenResponseDTO - = jwtTokenProvider.generateToken(new JwtDTO(phoneNumber)); + = jwtTokenProvider.generateToken(new JwtDTO(phoneNumber, checkMember.get().getRole().getDetail())); //액세스 + 리프레시 토큰 반환 return SmsCertificationSuccessResponseDTO.builder() diff --git a/src/main/java/com/tikitaka/naechinso/global/config/security/MemberAdapter.java b/src/main/java/com/tikitaka/naechinso/global/config/security/MemberAdapter.java index 35b6514..64227fd 100644 --- a/src/main/java/com/tikitaka/naechinso/global/config/security/MemberAdapter.java +++ b/src/main/java/com/tikitaka/naechinso/global/config/security/MemberAdapter.java @@ -1,11 +1,14 @@ package com.tikitaka.naechinso.global.config.security; +import com.tikitaka.naechinso.domain.member.constant.Role; import com.tikitaka.naechinso.domain.member.entity.Member; import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; /** * UserDetails 를 구현한 어댑터 @@ -18,8 +21,13 @@ public class MemberAdapter extends User { private Member member; public MemberAdapter(Member member) { - super(member.getPhone(), "", List.of(new SimpleGrantedAuthority("ROLE_USER"))); + super(member.getPhone(), "", authorities(member.getRole())); this.member = member; } + private static Collection authorities(Role userRole) { + Collection role = new ArrayList<>(); + role.add(new SimpleGrantedAuthority(userRole.getDetail())); + return role; + } } diff --git a/src/main/java/com/tikitaka/naechinso/global/config/security/UserDetailServiceImpl.java b/src/main/java/com/tikitaka/naechinso/global/config/security/UserDetailServiceImpl.java index 6b2e5d2..3d03d24 100644 --- a/src/main/java/com/tikitaka/naechinso/global/config/security/UserDetailServiceImpl.java +++ b/src/main/java/com/tikitaka/naechinso/global/config/security/UserDetailServiceImpl.java @@ -18,14 +18,9 @@ public class UserDetailServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException { Member member = memberRepository.findByPhone(phone) - .orElseThrow(() -> { throw new UsernameNotFoundException(phone + "-> DB에 없는 유저"); }); - + .orElseThrow(() -> { + throw new UsernameNotFoundException(phone + "-> DB에 없는 유저"); + }); return new MemberAdapter(member); -// return new User(member.getPhone(), "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); } -// -// //DB에 존재하는 유저일 경우 UserDetails로 만들어서 반환 -// private UserDetails createUserDetails(Member member) { -// GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_USER"); -// } } \ No newline at end of file diff --git a/src/main/java/com/tikitaka/naechinso/global/config/security/dto/JwtDTO.java b/src/main/java/com/tikitaka/naechinso/global/config/security/dto/JwtDTO.java index 7e6c0af..56e1a81 100644 --- a/src/main/java/com/tikitaka/naechinso/global/config/security/dto/JwtDTO.java +++ b/src/main/java/com/tikitaka/naechinso/global/config/security/dto/JwtDTO.java @@ -11,7 +11,7 @@ public class JwtDTO { // private Long id; private String phoneNumber; - private String authorities; + private String role; // public JwtDTO(User user) { //// this.id = user.getId(); @@ -20,9 +20,17 @@ public class JwtDTO { // this.authorities = user.getAuthorities().toString(); // } + /* 역할 정보가 없음 (registerToken) */ public JwtDTO(String phoneNumber) { this.phoneNumber = phoneNumber; //임시 권한 부여 - this.authorities = "ROLE_USER"; + this.role = null; + } + + /* 역할 정보가 있음 (accessToken) */ + public JwtDTO(String phoneNumber, String role) { + this.phoneNumber = phoneNumber; + //임시 권한 부여 + this.role = role; } } \ No newline at end of file diff --git a/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtAuthenticationFilter.java index 9a10146..2ab600d 100644 --- a/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtAuthenticationFilter.java @@ -46,7 +46,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // } if (StringUtils.isNotBlank(jwt) && jwtTokenService.validateToken(jwt)) { - Authentication authentication = jwtTokenService.getAuthentication(jwt); //authentication 획득 + Authentication authentication = jwtTokenService.getAuthentication(request, jwt); //authentication 획득 //Security 세션에서 계속 사용하기 위해 SecurityContext에 Authentication 등록 SecurityContextHolder.getContext().setAuthentication(authentication); @@ -55,9 +55,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse request.setAttribute("exception", ErrorCode.NO_TOKEN.getCode()); } - if (!jwtTokenService.validateToken(jwt, request)){ - - } + jwtTokenService.validateToken(request, jwt); } } catch (Exception ex) { logger.error("Security Context에 해당 토큰을 등록할 수 없습니다", ex); @@ -70,7 +68,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse filterChain.doFilter(request, response); } - private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); //Prefix 로 Bearer 가 있으면 제거 @@ -79,5 +76,6 @@ private String resolveToken(HttpServletRequest request) { } //Prefix 가 없으면 그대로 return bearerToken; + } } \ No newline at end of file diff --git a/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtTokenProvider.java b/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtTokenProvider.java index 000ba97..4d48ae1 100644 --- a/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtTokenProvider.java +++ b/src/main/java/com/tikitaka/naechinso/global/config/security/jwt/JwtTokenProvider.java @@ -32,6 +32,7 @@ public class JwtTokenProvider { private String JWT_SECRET; /** 토큰 유효 시간 (ms) */ + private static final long REGISTER_TOKEN_EXPIRATION_MS = 1000L * 60 * 60; //60분 private static final long JWT_EXPIRATION_MS = 1000L * 60 * 40; //40분 private static final long REFRESH_TOKEN_EXPIRATION_MS = 1000L * 60 * 60 * 24 * 7; //7일 private static final String AUTHORITIES_KEY = "role"; //권한 정보 컬럼명 @@ -49,7 +50,7 @@ public String generateAccessToken(JwtDTO jwtDTO) { .setIssuer("naechinso") .setIssuedAt(now) // 생성일자 지정(현재) .setSubject(jwtDTO.getPhoneNumber()) // 사용자(principal => phoneNumber) - .claim(AUTHORITIES_KEY, jwtDTO.getAuthorities()) //권한 설정 + .claim(AUTHORITIES_KEY, jwtDTO.getRole()) //권한 설정 .setExpiration(accessTokenExpiresIn) // 만료일자 .signWith(SignatureAlgorithm.HS512, encodedKey) // signature에 들어갈 secret 값 세팅 .compact(); @@ -95,24 +96,40 @@ public TokenResponseDTO generateToken(JwtDTO jwtDto) } /** 회원가입 전용 Register Token 생성 - * @param jwtDto 인증 요청하는 유저 정보 + * @param jwtDTO 인증 요청하는 유저 정보 */ - public String generateRegisterToken(JwtDTO jwtDto) + public String generateRegisterToken(JwtDTO jwtDTO) throws HttpServerErrorException.InternalServerError { - //권한 가져오기 - final String registerToken = generateAccessToken(jwtDto); + final String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes()); + final Date now = new Date(); + final Date registerTokenExpiresIn = new Date(now.getTime() + REGISTER_TOKEN_EXPIRATION_MS); + + //권한 정보를 제외하고 생성 + final String registerToken = Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setIssuer("naechinso") + .setIssuedAt(now) // 생성일자 지정(현재) + .setSubject(jwtDTO.getPhoneNumber()) // 사용자(principal => phoneNumber) + .setExpiration(registerTokenExpiresIn) // 만료일자 + .signWith(SignatureAlgorithm.HS512, encodedKey) // signature 에 들어갈 secret 값 세팅 + .compact(); + return registerToken; } - public Authentication getAuthentication(String accessToken) { + public Authentication getAuthentication(HttpServletRequest request, String accessToken) { Claims claims = parseClaims(accessToken); + System.out.println("claims = " + claims); // + if (claims.get(AUTHORITIES_KEY) == null) { + request.setAttribute("exception", ErrorCode.INVALID_AUTH_TOKEN.getCode()); throw new UnauthorizedException(ErrorCode.INVALID_AUTH_TOKEN); } UserDetails principal = loginService.loadUserByUsername(claims.getSubject()); + return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); } @@ -149,7 +166,7 @@ public boolean validateToken(String token) { * @param token 검사하려는 JWT 토큰 * @returns boolean * */ - public boolean validateToken(String token, HttpServletRequest request) { + public boolean validateToken(HttpServletRequest request, String token) { final String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes()); try { Jwts.parser().setSigningKey(encodedKey).parseClaimsJws(token); diff --git a/src/main/java/com/tikitaka/naechinso/global/error/ErrorResponse.java b/src/main/java/com/tikitaka/naechinso/global/error/ErrorResponse.java index 1233a13..45875ea 100644 --- a/src/main/java/com/tikitaka/naechinso/global/error/ErrorResponse.java +++ b/src/main/java/com/tikitaka/naechinso/global/error/ErrorResponse.java @@ -50,7 +50,7 @@ public static ErrorResponse of(ErrorCode errorCode, BindingResult bindingResult) public static JSONObject jsonOf(ErrorCode errorCode) { JSONObject jsonObject = new JSONObject(); jsonObject.put("timestamp", LocalDateTime.now()); - jsonObject.put("success", "false"); + jsonObject.put("success", false); jsonObject.put("message", errorCode.getDetail()); jsonObject.put("status", errorCode.getHttpStatus().value()); jsonObject.put("code", errorCode.getCode()); From 54be8d116538c600cecac3c21607697a242ea9ad Mon Sep 17 00:00:00 2001 From: gengminy Date: Sun, 2 Oct 2022 01:04:26 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:rocket:=20feat(join):=20=EA=B0=80=EC=9E=85?= =?UTF-8?q?=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=B4=88=EA=B8=B0=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1=20#20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/recommend/RecommendController.java | 72 ++++++------ .../domain/recommend/RecommendRepository.java | 5 + .../domain/recommend/RecommendService.java | 75 +++++++++++-- .../dto/RecommendAcceptRequestDTO.java | 103 ++++++++++++++++++ .../domain/recommend/dto/RecommendDTO.java | 12 +- .../recommend/dto/RecommendMetaDTO.java | 18 --- .../recommend/dto/RecommendRequestDTO.java | 9 +- .../domain/recommend/entity/Recommend.java | 11 +- .../config/security/SecurityConfig.java | 1 + .../naechinso/global/error/ErrorCode.java | 5 + 10 files changed, 237 insertions(+), 74 deletions(-) create mode 100644 src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendAcceptRequestDTO.java delete mode 100644 src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendMetaDTO.java diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java index ffc1049..fe806a4 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendController.java @@ -1,10 +1,7 @@ package com.tikitaka.naechinso.domain.recommend; import com.tikitaka.naechinso.domain.member.entity.Member; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendDTO; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendJoinRequestDTO; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendListResponseDTO; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendRequestDTO; +import com.tikitaka.naechinso.domain.recommend.dto.*; import com.tikitaka.naechinso.global.annotation.AuthMember; import com.tikitaka.naechinso.global.config.CommonApiResponse; import com.tikitaka.naechinso.global.config.security.jwt.JwtTokenProvider; @@ -19,6 +16,7 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; +import java.util.List; @Slf4j @RestController @@ -53,6 +51,17 @@ public CommonApiResponse createRecommendNewSender( return CommonApiResponse.of(recommendDTO); } + @GetMapping("/find") + @ApiOperation(value = "[Admin]모든 추천사 정보를 가져온다 (AccessToken)") + public CommonApiResponse> getAllRecommends( + @ApiIgnore @AuthMember Member member) + { + if (member == null) { + throw new UnauthorizedException(ErrorCode.UNAUTHORIZED_USER); + } + return CommonApiResponse.of(recommendService.findAll()); + } + @PostMapping("/request") @ApiOperation(value = "다른 유저에게 추천서 작성을 요청한다 (registerToken 필요)") public CommonApiResponse createRecommendRequest( @@ -71,36 +80,35 @@ public CommonApiResponse createRecommendRequest( + //제일 아래에 있어야함 + @GetMapping("/{uuid}") + @ApiOperation(value = "추천 요청받은 uuid 를 가진 추천사 정보를 가져온다 (Register / AccessToken 필요)") + public CommonApiResponse getRecommendByUuid( + HttpServletRequest request, + @PathVariable("uuid") String uuid) + { + //토큰 장착 확인 + String token = request.getHeader("Authorization"); + String phone = parseRegisterToken(token); + + RecommendDTO recommendDTO = RecommendDTO.of(recommendService.findByUuid(uuid)); + return CommonApiResponse.of(recommendDTO); + } -// //제일 아래에 있어야함 -// @GetMapping("/{uuid}") -// @ApiOperation(value = "추천 요청받은 uuid 를 가진 추천사 정보를 가져온다") -// public CommonApiResponse getRecommendByUuid( -// HttpServletRequest request, -// @PathVariable("uuid") String uuid, -// @Valid @RequestBody RecommendRequestDTO dto) -// { -// String registerToken = request.getHeader("Authorization"); -// String phone = parseRegisterToken(registerToken); -// -//// RecommendDTO recommendDTO = recommendService.findAllRecommendRequestsByUuid(uuid); -// return CommonApiResponse.of(recommendDTO); -// } + @PatchMapping("/{uuid}") + @ApiOperation(value = "요청받은 uuid 추천사에 자신을 추천인으로 등록한다 (Register / AccessToken 필요)") + public CommonApiResponse getRecommendByUuid( + HttpServletRequest request, + @PathVariable("uuid") String uuid, + @Valid @RequestBody RecommendAcceptRequestDTO dto) + { + //토큰 장착 확인 + String token = request.getHeader("Authorization"); + String phone = parseRegisterToken(token); - // //제일 아래에 있어야함 -// @PatchMapping("/{uuid}") -// @ApiOperation(value = "요청받은 uuid 추천사에 추천인을 등록한다") -// public CommonApiResponse getRecommendByUuid( -// HttpServletRequest request, -// @PathVariable("uuid") String uuid, -// @Valid @RequestBody RecommendRequestDTO dto) -// { -// String registerToken = request.getHeader("Authorization"); -// String phone = parseRegisterToken(registerToken); -// -//// RecommendDTO recommendDTO = recommendService.findAllRecommendRequestsByUuid(uuid); -// return CommonApiResponse.of(recommendDTO); -// } + RecommendDTO recommendDTO = recommendService.updateRecommendRequest(uuid, phone, dto); + return CommonApiResponse.of(recommendDTO); + } private String parseRegisterToken(String registerToken) { if (StringUtils.isBlank(registerToken) || !jwtTokenService.validateToken(registerToken)) { diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendRepository.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendRepository.java index da597a8..a0c2ae7 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendRepository.java +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendRepository.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Optional; +import java.util.UUID; @Repository public interface RecommendRepository extends JpaRepository { @@ -16,5 +17,9 @@ public interface RecommendRepository extends JpaRepository { List findAllBySender_Id(Long id); List findAllBySenderPhone(String phone); + List findAllByIdNotNull(); + + Optional findByUuid(String uuid); + Boolean existsByReceiverPhone(String phone); } diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendService.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendService.java index 67c80f3..975a2b1 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendService.java +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/RecommendService.java @@ -2,10 +2,7 @@ import com.tikitaka.naechinso.domain.member.MemberRepository; import com.tikitaka.naechinso.domain.member.entity.Member; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendDTO; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendJoinRequestDTO; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendListResponseDTO; -import com.tikitaka.naechinso.domain.recommend.dto.RecommendRequestDTO; +import com.tikitaka.naechinso.domain.recommend.dto.*; import com.tikitaka.naechinso.domain.recommend.entity.Recommend; import com.tikitaka.naechinso.global.error.ErrorCode; import com.tikitaka.naechinso.global.error.exception.BadRequestException; @@ -17,6 +14,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; @Service @RequiredArgsConstructor @@ -39,6 +37,11 @@ public RecommendDTO createRecommendJoin(String senderPhone, RecommendJoinRequest throw new BadRequestException(ErrorCode.USER_ALREADY_EXIST); } + //자기 자신을 추천하면 종료 + if (senderPhone == dto.getReceiverPhone()) { + throw new BadRequestException(ErrorCode.CANNOT_RECOMMEND_MYSELF); + } + Member sender = dto.toSender(senderPhone); Member receiver = memberRepository.findByPhone(dto.getReceiverPhone()) @@ -70,6 +73,11 @@ public RecommendDTO createRecommendJoin(String senderPhone, RecommendJoinRequest } public RecommendDTO createRecommendRequest(String receiverPhone, RecommendRequestDTO dto) { + //이미 추천사 요청을 보냄 + if (existsByReceiverPhone(receiverPhone)) { + throw(new NotFoundException(ErrorCode.RECOMMEND_ALREADY_EXIST)); + } + Member receiver = dto.toReceiver(receiverPhone); Recommend recommend = Recommend.builder() .sender(null) @@ -86,6 +94,55 @@ public RecommendDTO createRecommendRequest(String receiverPhone, RecommendReques return RecommendDTO.of(recommend); } + public RecommendDTO updateRecommendRequest(String uuid, String senderPhone, RecommendAcceptRequestDTO dto) { + + Recommend recommend = findByUuid(uuid); + + //자기 자신을 추천하면 종료 + if (senderPhone == recommend.getReceiverPhone()) { + throw new BadRequestException(ErrorCode.CANNOT_RECOMMEND_MYSELF); + } + + //유저가 없으면 회원가입 시킴 있으면 그대로 사용 + Member sender = memberRepository.findByPhone(senderPhone) + .orElse(dto.toSender(senderPhone)); + + recommend.setSender(sender); + recommend.setSenderPhone(senderPhone); + recommend.setSenderName(dto.getName()); + recommend.setSenderAge(dto.getAge()); + recommend.setSenderGender(dto.getGender()); + recommend.setSenderJobName(dto.getJobName()); + recommend.setSenderJobPart(dto.getJobPart()); + recommend.setSenderJobLocation(dto.getJobLocation()); + recommend.setReceiverName(dto.getReceiverName()); + recommend.setReceiverAge(dto.getReceiverAge()); + recommend.setReceiverAppeal(dto.getAppeal()); + recommend.setReceiverGender(dto.getReceiverGender()); + recommend.setReceiverMeet(dto.getMeet()); + recommend.setReceiverPersonality(dto.getPersonality()); + + memberRepository.save(sender); + recommendRepository.save(recommend); + + return RecommendDTO.of(recommend); + } + + + public List findAll() { + List recommendDTOList = new ArrayList<>(); + + recommendRepository.findAllByIdNotNull().forEach( + recommend -> recommendDTOList.add(RecommendDTO.of(recommend)) + ); + + recommendDTOList.forEach( + recommendDTO -> System.out.println("recommendDTO = " + recommendDTO) + ); + + return recommendDTOList; + } + public List findAllBySenderPhone(String phone) { List recommendDTOList = new ArrayList<>(); recommendRepository.findAllBySenderPhone(phone).stream().map( @@ -94,12 +151,10 @@ public List findAllBySenderPhone(String phone) { return recommendDTOList; } -// //링크로 요청한 -// public RecommendDTO findAllRecommendRequestsByUuid(String uuid){ -// RecommendMeta meta = recommendMetaRepository.findByUuid(uuid) -// .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); -// return RecommendDTO.of(meta.getRecommend()); -// } + public Recommend findByUuid(String uuid) { + return recommendRepository.findByUuid(uuid) + .orElseThrow(() -> new NotFoundException(ErrorCode.RECOMMEND_NOT_FOUND)); + } public Boolean existsByReceiverPhone(String phone){ return recommendRepository.existsByReceiverPhone(phone); diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendAcceptRequestDTO.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendAcceptRequestDTO.java new file mode 100644 index 0000000..51c2d96 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendAcceptRequestDTO.java @@ -0,0 +1,103 @@ +package com.tikitaka.naechinso.domain.recommend.dto; + +import com.tikitaka.naechinso.domain.member.constant.Gender; +import com.tikitaka.naechinso.domain.member.entity.Member; +import com.tikitaka.naechinso.global.annotation.Enum; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +import javax.validation.constraints.*; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Builder +@ToString +public class RecommendAcceptRequestDTO { + @ApiModelProperty(example = "닉") + @NotBlank(message = "이름을 입력해주세요") + private String name; + + @ApiModelProperty(example = "M") + @Enum(enumClass = Gender.class, message = "성별 입력이 올바르지 않습니다. M 또는 W가 필요합니다") + private Gender gender; + + @ApiModelProperty(example = "25") + @Min(value = 25, message = "25-33세까지만 가입 가능합니다") + @Max(value = 33, message = "25-33세까지만 가입 가능합니다") + private int age; + + @NotNull(message = "서비스 이용약관 동의가 필요합니다") + @AssertTrue(message = "서비스 이용약관 동의가 필요합니다") + private boolean acceptsService; + + @NotNull(message = "개인정보 이용 동의가 필요합니다") + @AssertTrue(message = "개인정보 이용 동의가 필요합니다") + private boolean acceptsInfo; + + @NotNull(message = "종교 정보 제공 동의가 필요합니다") + @AssertTrue(message = "종교 정보 제공 동의가 필요합니다") + private boolean acceptsReligion; + + @NotNull(message = "위치 정보 제공 동의 여부가 필요합니다") + private boolean acceptsLocation; + + @NotNull(message = "마케팅 동의 여부가 필요합니다") + private boolean acceptsMarketing; + + @ApiModelProperty(example = "카카오") + @NotBlank(message = "직장명을 입력해주세요") + private String jobName; + + @ApiModelProperty(example = "개발자") + @NotBlank(message = "직장 부서를 입력해주세요") + private String jobPart; + + @ApiModelProperty(example = "판교") + @NotBlank(message = "직장 위치를 입력해주세요") + private String jobLocation; + + + @ApiModelProperty(example = "박스") + @NotBlank(message = "친구의 이름을 입력해주세요") + private String receiverName; + + @ApiModelProperty(example = "M") + @Enum(enumClass = Gender.class, message = "친구의 성별 입력이 올바르지 않습니다. M 또는 W가 필요합니다") + private Gender receiverGender; + + @ApiModelProperty(example = "25") + @Min(value = 25, message = "25-33세까지만 추천 및 가입 가능합니다") + @Max(value = 33, message = "25-33세까지만 추천 및 가입 가능합니다") + private int receiverAge; + + @ApiModelProperty(example = "CMC 에서") + @NotBlank(message = "만나게 된 계기를 입력해주세요") + private String meet; + + @ApiModelProperty(example = "최고") + @NotBlank(message = "친구의 성격 키워드를 입력해주세요") + private String personality; + + @ApiModelProperty(example = "짱") + @NotBlank(message = "친구의 매력을 입력해주세요") + private String appeal; + + + public Member toSender(String phone){ + return Member.builder() + .phone(phone) + .name(this.name) + .gender(this.gender) + .age(this.age) + .acceptsService(this.acceptsService) + .acceptsInfo(this.acceptsInfo) + .acceptsReligion(this.acceptsReligion) + .acceptsLocation(this.acceptsLocation) + .acceptsMarketing(this.acceptsMarketing) + .jobName(this.jobName) + .jobPart(this.jobPart) + .jobLocation(this.jobLocation) + .build(); + } +} diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendDTO.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendDTO.java index 573b7da..5ad551b 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendDTO.java +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendDTO.java @@ -6,6 +6,7 @@ import lombok.*; import javax.persistence.*; +import java.util.UUID; @AllArgsConstructor @NoArgsConstructor @@ -14,11 +15,9 @@ @ToString public class RecommendDTO { - private String phone; - - private Long senderId; + private String uuid; - private Long receiverId; + private String phone; private String name; @@ -28,6 +27,10 @@ public class RecommendDTO { private String appeal; + private Long senderId; + + private Long receiverId; + public static RecommendDTO of(Recommend recommend) { Long senderId; @@ -45,6 +48,7 @@ public static RecommendDTO of(Recommend recommend) { } return RecommendDTO.builder() + .uuid(recommend.getUuid()) .phone(recommend.getReceiverPhone()) .senderId(senderId) .receiverId(receiverId) diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendMetaDTO.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendMetaDTO.java deleted file mode 100644 index 69c16df..0000000 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendMetaDTO.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.tikitaka.naechinso.domain.recommend.dto; - -import com.tikitaka.naechinso.domain.member.constant.Gender; -import com.tikitaka.naechinso.domain.recommend.entity.Recommend; -import lombok.*; - -import java.util.UUID; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -@Builder -@ToString -public class RecommendMetaDTO { - private UUID uuid; - - private Recommend recommend; -} diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendRequestDTO.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendRequestDTO.java index eced9bf..cc132e3 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendRequestDTO.java +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/dto/RecommendRequestDTO.java @@ -3,6 +3,7 @@ import com.tikitaka.naechinso.domain.member.constant.Gender; import com.tikitaka.naechinso.domain.member.entity.Member; import com.tikitaka.naechinso.global.annotation.Enum; +import io.swagger.annotations.ApiModelProperty; import lombok.*; import javax.persistence.Column; @@ -16,12 +17,15 @@ @Builder @ToString public class RecommendRequestDTO { + @ApiModelProperty(example = "닉") @NotBlank(message = "유저 이름을 입력해주세요") private String name; + @ApiModelProperty(example = "M") @Enum(enumClass = Gender.class, message = "유저의 성별 입력이 올바르지 않습니다. M 또는 W가 필요합니다") private Gender gender; + @ApiModelProperty(example = "25") @Min(value = 25, message = "25-33세까지만 추천 및 가입 가능합니다") @Max(value = 33, message = "25-33세까지만 추천 및 가입 가능합니다") private int age; @@ -44,11 +48,6 @@ public class RecommendRequestDTO { @NotNull(message = "마케팅 동의 여부가 필요합니다") private boolean acceptsMarketing; - //링크 식별을 위한 uuid - @Builder.Default - private String uuid = UUID.randomUUID().toString(); - - public Member toReceiver(String phone) { return Member.builder() .name(this.getName()) diff --git a/src/main/java/com/tikitaka/naechinso/domain/recommend/entity/Recommend.java b/src/main/java/com/tikitaka/naechinso/domain/recommend/entity/Recommend.java index 906512f..ca4fe1d 100644 --- a/src/main/java/com/tikitaka/naechinso/domain/recommend/entity/Recommend.java +++ b/src/main/java/com/tikitaka/naechinso/domain/recommend/entity/Recommend.java @@ -6,8 +6,10 @@ import com.tikitaka.naechinso.domain.recommend.dto.RecommendJoinRequestDTO; import com.tikitaka.naechinso.global.config.entity.BaseEntity; import lombok.*; +import org.hibernate.annotations.GenericGenerator; import javax.persistence.*; +import java.util.UUID; /** * 추천사 정보를 담당하는 엔티티입니다 @@ -16,6 +18,7 @@ @Entity @Table(name = "recommend") @Getter +@Setter @Builder @AllArgsConstructor @NoArgsConstructor @@ -27,6 +30,9 @@ public class Recommend extends BaseEntity { @Column(name = "rec_id") private Long id; + @Builder.Default + private String uuid = UUID.randomUUID().toString(); + /* 추천 해준 사람 */ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "mem_id") @@ -82,9 +88,4 @@ public class Recommend extends BaseEntity { @Column(name = "rec_appeal") private String receiverAppeal; - public static Recommend of(RecommendJoinRequestDTO dto) { - return Recommend.builder() - .build(); - } - } diff --git a/src/main/java/com/tikitaka/naechinso/global/config/security/SecurityConfig.java b/src/main/java/com/tikitaka/naechinso/global/config/security/SecurityConfig.java index 63a0f04..a278acd 100644 --- a/src/main/java/com/tikitaka/naechinso/global/config/security/SecurityConfig.java +++ b/src/main/java/com/tikitaka/naechinso/global/config/security/SecurityConfig.java @@ -59,6 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .antMatchers("/member/join").permitAll() .antMatchers("/sms/**").permitAll() .antMatchers("/recommend/").permitAll() + .antMatchers("/recommend/request").permitAll() .anyRequest().authenticated() .and() .headers().frameOptions().disable(); diff --git a/src/main/java/com/tikitaka/naechinso/global/error/ErrorCode.java b/src/main/java/com/tikitaka/naechinso/global/error/ErrorCode.java index 4dd7a1c..4f9ce91 100644 --- a/src/main/java/com/tikitaka/naechinso/global/error/ErrorCode.java +++ b/src/main/java/com/tikitaka/naechinso/global/error/ErrorCode.java @@ -47,6 +47,11 @@ public enum ErrorCode { USER_NOT_FOUND(NOT_FOUND, "U003","해당 유저 정보를 찾을 수 없습니다"), NOT_FOLLOW(NOT_FOUND, "U004","팔로우 중이지 않습니다"), + /* Recommend 관련 오류 */ + RECOMMEND_NOT_FOUND(NOT_FOUND, "R000","해당 추천사 정보를 찾을 수 없습니다"), + RECOMMEND_ALREADY_EXIST(BAD_REQUEST, "R001","추천사 요청은 한 번만 보낼 수 있습니다"), + CANNOT_RECOMMEND_MYSELF(BAD_REQUEST, "R002","자기 자신은 추천할 수 없습니다"), + /* Validation 오류 */ PARAMETER_NOT_VALID(BAD_REQUEST, "P000", "인자가 유효하지 않습니다"),