Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 사용자 가입승인 기능 구현 #26

Merged
merged 80 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
4e3a3ea
feat : 사용자 로그인 기능
jw427 Aug 23, 2024
5d7326e
refactor : 파일명과 변수명에서 게시물을 뜻하는 content를 post로 변경
rhaehf Aug 24, 2024
82c10bc
refactor : Long에서 String으로 자료형 변경
rhaehf Aug 24, 2024
99ebf13
feat : 로그인시 리프레시토큰 저장 및 리프레시토큰과 액세스토큰 재발급
jw427 Aug 24, 2024
97059e4
feat : users SecurityConfig 비밀번호 암호화 기능 추가
jeongeungyeong Aug 25, 2024
41cd12e
refactor : Post 엔티티 수정
pie2457 Aug 25, 2024
4715204
refactor : ContentRepository 삭제
pie2457 Aug 25, 2024
a44386f
build : QueryDsl dependency 추가
pie2457 Aug 25, 2024
d1e4d20
chore : QueryDslConfig 클래스 생성
pie2457 Aug 25, 2024
2c28a09
feat : String으로 받아온 파라미터를 LocalDateTime으로 바꿔주는 Converter 설정
pie2457 Aug 25, 2024
a80f8a2
feat : 게시물 통계 기능 구현
pie2457 Aug 25, 2024
c82cce5
refactor : ErrorResponse 클래스를 record 클래스로 변경
pie2457 Aug 25, 2024
32ec4f7
feat : 400에러를 처리하기 위한 Exception 생성
pie2457 Aug 25, 2024
2d8e04e
feat : api 테스트를 위한 security 임의 설정
pie2457 Aug 25, 2024
7851d34
refactor : 리뷰사항 반영
pie2457 Aug 25, 2024
24fbc92
refactor : 리뷰사항 반영
pie2457 Aug 25, 2024
5c6d9b8
feat : api 테스트를 위한 security 임시 설정
jeongeungyeong Aug 25, 2024
41909aa
feat : Code 클래스 @Builder 추가
jeongeungyeong Aug 25, 2024
2ee34cb
feat : 사용자 회원가입 기능 구현
jeongeungyeong Aug 25, 2024
3765657
refactor : 가독성 위한 코드 수정 및 에러 핸들링 추가
jw427 Aug 25, 2024
d888f83
refactor : 토큰 만료시간 수정
jw427 Aug 25, 2024
848ce30
refactor : 필드 공백 제거
jw427 Aug 25, 2024
979198c
refactor : 코드 재발급 메서드 명칭 수정
jw427 Aug 25, 2024
e2ff106
refactor : 로그인 메서드 명칭 ㅅ정
jw427 Aug 25, 2024
9455bed
refactor : TokenRequestDto record 클래스로 수정
jw427 Aug 25, 2024
e0ac706
fix : String 타입은 IDENTITY 적용할 수 없어서 제거함
rhaehf Aug 25, 2024
e21ab02
refactor : JPA 변경 감지 기능에 따른 save() 메서드 삭제
jw427 Aug 25, 2024
382d032
refactor : Service에 트랜잭션 설정 추가
jw427 Aug 25, 2024
22cfabc
feat : 게시글 좋아요 기능 구현
rhaehf Aug 25, 2024
720d726
refactor :TokenProvider에 상수 변경
jw427 Aug 25, 2024
6791477
refactor : TokenRepository 쿼리문 삭제
jw427 Aug 25, 2024
a5a25d7
refactor : UserCreateDto → UserInfoDto로 이름 변경
jeongeungyeong Aug 25, 2024
49858f2
refactor : UserCreateDto → UserInfoDto로 이름 변경
jeongeungyeong Aug 25, 2024
c0c760d
feat : 회원등급 속성 추가
jeongeungyeong Aug 25, 2024
59b7b02
feat : UserInfoDto 회원등급 속성 추가
jeongeungyeong Aug 25, 2024
01e65fa
feat : 사용자 가입승인 기능 구현
jeongeungyeong Aug 25, 2024
e97334b
style : 패키지명 변경
pie2457 Aug 26, 2024
e330442
refactor : yml 파일 추가
jw427 Aug 26, 2024
fd06fb4
Merge pull request #19 from 2024-pre-onboarding-backend-F/refactor/mo…
pie2457 Aug 26, 2024
44cf198
fix : 동시성 문제로 인한 에러 해결
rhaehf Aug 26, 2024
1f12dd4
refactor : 게시물 좋아요 기능 로직 파일 변경 PostLike~ → Post~
rhaehf Aug 26, 2024
cee2975
feat : PostIdResponse를 사용하여 응답 형식 구조 개선
rhaehf Aug 26, 2024
3f66cc0
Merge pull request #20 from 2024-pre-onboarding-backend-F/feat/statis…
pie2457 Aug 26, 2024
ca15823
refactor : dev yml파일 수정
jw427 Aug 26, 2024
c7c8031
refactor : 필드 공백 제거
jeongeungyeong Aug 26, 2024
fb58e73
refactor : addLikeCount()의 파라미터 제거로 간결하게 개선
rhaehf Aug 26, 2024
b93636e
no message
rhaehf Aug 26, 2024
f8863e6
refactor : SignUpRequset, SignUpResponse, UserCreateDto record 클래스로 변경
jeongeungyeong Aug 26, 2024
db0a386
refactor : access level 설정
jeongeungyeong Aug 26, 2024
7b88754
refactor : 통상적으로 자주 사용되는 비밀번호 검사 코드 수정
jeongeungyeong Aug 26, 2024
c107f78
feat : 인증코드 발급 방식을 uuid로 변경
jeongeungyeong Aug 26, 2024
18a237c
refactor : record 클래스 변경으로 회원가입 api 코드 수정
jeongeungyeong Aug 26, 2024
a5649ef
refactor : 필드 공백 제거
jeongeungyeong Aug 26, 2024
be4f767
refactor : 필드 공백 제거
jeongeungyeong Aug 26, 2024
cbdc2bc
refactor : PostIdResponse를 에러 반환에 적용하여 응답 형식 개선
rhaehf Aug 26, 2024
9e1b67c
refactor : 통상적으로 사용되는 비밀번호 검증 삭제
jeongeungyeong Aug 26, 2024
0cc447b
Merge branch 'dev' into feat/user_security_config
jeongeungyeong Aug 26, 2024
ff432ae
Merge pull request #21 from 2024-pre-onboarding-backend-F/feat/user_s…
jeongeungyeong Aug 26, 2024
4f3e5e6
Merge branch 'dev' into feat/user_sign_up
jeongeungyeong Aug 26, 2024
8996a0b
Merge pull request #23 from 2024-pre-onboarding-backend-F/feat/user_s…
jeongeungyeong Aug 26, 2024
7f57a62
refactor : 로그인시 비밀번호 암호화 적용 추가
jw427 Aug 26, 2024
5e16349
[feat] post 상세보기 api (#27)
ssunnykku Aug 26, 2024
4d40bbb
fix : 충돌 해결
jw427 Aug 26, 2024
c78da78
Merge pull request #22 from 2024-pre-onboarding-backend-F/feat/login
jw427 Aug 26, 2024
a47f35b
feat : 에러를 처리하기 위한 Exception 생성
jeongeungyeong Aug 26, 2024
45b8c68
refactor : VerifyRequest, VerifyResponse, UserInfoDto radio 클래스로 변경
jeongeungyeong Aug 26, 2024
89b64f1
refactor : Exception 코드 수정
jeongeungyeong Aug 26, 2024
4984314
feat : 사용자 인증코드 조회 쿼리 추가
jeongeungyeong Aug 26, 2024
8abedde
feat : 인증코드 재발급 기능 추가
jeongeungyeong Aug 26, 2024
712d6ad
Merge branch 'dev' into feat/posts_like
rhaehf Aug 26, 2024
3cf7a40
Merge pull request #28 from 2024-pre-onboarding-backend-F/feat/posts_…
rhaehf Aug 26, 2024
f2ff33a
Changes
jeongeungyeong Aug 26, 2024
a8dbf4d
feat : 회원등급 속성 추가
jeongeungyeong Aug 25, 2024
8dc959d
Changes
jeongeungyeong Aug 26, 2024
5d6de40
Changes
jeongeungyeong Aug 26, 2024
98de95f
refactor : VerifyRequest, VerifyResponse, UserInfoDto radio 클래스로 변경
jeongeungyeong Aug 26, 2024
6873fb3
Changes
jeongeungyeong Aug 26, 2024
6756816
feat : 사용자 인증코드 조회 쿼리 추가
jeongeungyeong Aug 26, 2024
905d212
Changes
jeongeungyeong Aug 26, 2024
361ad30
Changes
jeongeungyeong Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/main/java/wanted/media/user/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
package wanted.media.user.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class SecurityConfig {
// 비밀번호 암호화 기능
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// 임시 설정
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.anyRequest().permitAll() // 모든 요청 허용
)
.csrf().disable() // CSRF 보호 비활성화 (테스트 목적으로만 사용)
.formLogin().disable() // 로그인 폼 비활성화
.httpBasic().disable(); // HTTP Basic 인증 비활성화

return http.build();
}
}
29 changes: 28 additions & 1 deletion src/main/java/wanted/media/user/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
package wanted.media.user.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import wanted.media.user.dto.SignUpRequest;
import wanted.media.user.dto.SignUpResponse;
import wanted.media.user.dto.VerifyRequest;
import wanted.media.user.dto.VerifyResponse;
import wanted.media.user.service.UserService;

@RestController
@RequestMapping("/user")
@RequestMapping("/api/user")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url은 "/api/users" 복수형으로 수정해야할 것 같습니다.

@RequiredArgsConstructor
public class UserController {

private final UserService userService;

// 회원가입 API
@PostMapping("/sign-up")
public ResponseEntity<SignUpResponse> signUp(@Validated @RequestBody SignUpRequest request) {
SignUpResponse response = userService.signUp(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

/*
* 가입승인 API
* 회원등급 (normal -> premium)
* */
@PostMapping("/approve")
public ResponseEntity<VerifyResponse> approveSignUp(@RequestBody VerifyRequest request) {
VerifyResponse response = userService.approveSignUp(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

}
4 changes: 3 additions & 1 deletion src/main/java/wanted/media/user/domain/Code.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
Expand All @@ -12,6 +13,7 @@
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder는 어디에 사용되나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder는 어디에 사용되나요?

Code 객체 생성에 사용됩니다. 인증코드 관련 정보를 설정하기 위해서 사용했습니다.

@Entity
@Table(name = "codes")
public class Code {
Expand All @@ -21,7 +23,7 @@ public class Code {
@Column(nullable = false)
private Long codeId;

@OneToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/wanted/media/user/dto/SignUpRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package wanted.media.user.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data
public class SignUpRequest {
@NotBlank
@Size(max = 50)
private String account;

@NotBlank
@Email
@Size(max = 50)
private String email;

@NotBlank
@Size(min = 10, max = 200, message = "비밀번호는 최소 10자리 이상으로 설정해주세요.")
private String password;
}
14 changes: 14 additions & 0 deletions src/main/java/wanted/media/user/dto/SignUpResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package wanted.media.user.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class SignUpResponse {
private String message;
private UserInfoDto userInfoDto; // 사용자 정보 DTO
private String authCode; // 사용자 인증코드
}
15 changes: 15 additions & 0 deletions src/main/java/wanted/media/user/dto/UserInfoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package wanted.media.user.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import wanted.media.user.domain.Grade;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class UserInfoDto {
private String account;
private String email;
private Grade grade; // 현재 회원등급
}
20 changes: 20 additions & 0 deletions src/main/java/wanted/media/user/dto/VerifyRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package wanted.media.user.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data
public class VerifyRequest {
@NotBlank
@Size(max = 50)
private String account;

@NotBlank
@Size(min = 10, max = 200)
private String password;

@NotBlank
@Size(max = 10)
private String inputCode; //사용자 입력 인증코드
}
13 changes: 13 additions & 0 deletions src/main/java/wanted/media/user/dto/VerifyResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package wanted.media.user.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class VerifyResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Response, Request, Dto 전부 record 클래스로 변경하면 깔끔해지겠네요 !

private String message;
private UserInfoDto userInfo; //사용자 정보
}
15 changes: 15 additions & 0 deletions src/main/java/wanted/media/user/repository/CodeRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package wanted.media.user.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import wanted.media.user.domain.Code;
import wanted.media.user.domain.User;

import java.util.Optional;

public interface CodeRepository extends JpaRepository<Code, Long> {
//인증코드 검증
Optional<Code> findByUserAndAuthCode(User user, String authCode);

//사용자가 발급받은 인증코드 삭제
void deleteByUser(User user);
}
15 changes: 15 additions & 0 deletions src/main/java/wanted/media/user/repository/UserRepository.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
package wanted.media.user.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import wanted.media.user.domain.Grade;
import wanted.media.user.domain.User;

import java.util.Optional;
import java.util.UUID;

public interface UserRepository extends JpaRepository<User, UUID> {
// 사용자 계정으로 회원 조회
Optional<User> findByAccount(String account);

// 사용자 이메일로 회원 조회
Optional<User> findByEmail(String email);

// 가입인증 회원 등급 변경
@Modifying
@Query("UPDATE User u SET u.grade = :grade WHERE u.account = :account")
void updateUserGrade(@Param("account") String account, @Param("grade") Grade grade);
}
24 changes: 24 additions & 0 deletions src/main/java/wanted/media/user/service/GenerateCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package wanted.media.user.service;

import org.springframework.stereotype.Component;

import java.util.Random;

@Component
public class GenerateCode {
private int codeLength = 6; //6자리 코드
private final char[] characterTable = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};

public String codeGenerate() {
Random random = new Random(System.currentTimeMillis());
int tableLength = characterTable.length;
StringBuilder code = new StringBuilder();

for (int i = 0; i < codeLength; i++) {
code.append(characterTable[random.nextInt(tableLength)]);
}
return code.toString();
}
}
77 changes: 77 additions & 0 deletions src/main/java/wanted/media/user/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,89 @@
package wanted.media.user.service;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import wanted.media.user.domain.Code;
import wanted.media.user.domain.Grade;
import wanted.media.user.domain.User;
import wanted.media.user.dto.*;
import wanted.media.user.repository.CodeRepository;
import wanted.media.user.repository.UserRepository;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Transactional
public class UserService {

private final UserRepository userRepository;
private final CodeRepository codeRepository;
private final BCryptPasswordEncoder passwordEncoder;
private final UserValidator userValidator;
private final GenerateCode generateCode;


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

20,27라인 공백 삭제해주세용

//회원가입
public SignUpResponse signUp(SignUpRequest request) {
// 1. 사용자 입력내용 검증
userValidator.validateRequest(request);
// 2. 비밀번호 암호화
String encodedPassword = passwordEncoder.encode(request.getPassword());
// 3. 인증코드 생성
String verificationCode = generateCode.codeGenerate();
// 4. User 객체 생성
User user = User.builder()
.account(request.getAccount())
.email(request.getEmail())
.password(encodedPassword)
.grade(Grade.NORMAL_USER)
.build();
// 5. 사용자 db 저장
userRepository.save(user);
// 6. Code 객체 생성
Code code = Code.builder()
.user(user)
.authCode(verificationCode)
.createdTime(LocalDateTime.now())
.build();
// 7. 인증코드 db 저장
codeRepository.save(code);
// 8. UserInfoDto 생성
UserInfoDto userInfoDto = new UserInfoDto(user.getAccount(), user.getEmail(), user.getGrade());
// 9. SignUpResponse 생성
SignUpResponse signUpResponse = new SignUpResponse("회원가입이 성공적으로 완료됐습니다.", userInfoDto, verificationCode);

return signUpResponse;
}

//가입승인
public VerifyResponse approveSignUp(VerifyRequest verifyRequest) {
// 1. account로 사용자 조회
User user = userRepository.findByAccount(verifyRequest.getAccount())
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception은 해당 에러와 맞춰서 내야 할 것 같습니다 사용자를 찾을 수 없는 에러라면 NoFoundException이 맞겠죵?
아래에 있는 Exception도 에러 메세지와 맞게 변경해주세요~

// 2. 비밀번호 검증
if (!passwordEncoder.matches(verifyRequest.getPassword(), user.getPassword())) {
throw new RuntimeException("비밀번호가 일치하지 않습니다.");
}
// 3. 사용자 인증코드 검증
Code code = codeRepository.findByUserAndAuthCode(user, verifyRequest.getInputCode())
.orElseThrow(() -> new RuntimeException("인증코드가 일치하지 않습니다."));
// 4. 인증코드 유효성 검증 (유효시간 15분)
if (code.getCreatedTime().plusMinutes(15).isBefore(LocalDateTime.now())) {
throw new RuntimeException("만료된 인증코드입니다.");
}
// 5. 인증 완료 -> 회원 등급 변경 (normal -> premium)
userRepository.updateUserGrade(user.getAccount(), Grade.PREMIUM_USER);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 JPA 변경감지 기능을 사용하셔야 할 것 같아요 ~

// 6. 인증 완료 회원 인증코드 삭제
codeRepository.deleteByUser(user);
// 7. 변경된 사용자정보 다시 조회
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변경된 사용자 정보는 왜 다시 조회해야 하나요?👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변경된 사용자 정보는 왜 다시 조회해야 하나요?👀

회원등급을 PREMIUM_USER로 업데이트한 후, 최신 사용자 정보를 반환하기 위해 변경된 정보를 다시 조회하도록 했습니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 해당 메서드 내에 user가 있어서 (메서드 시작 부분에 findByAccount로 불러온 User 객체) 레포지토리에서 새로 가져오는 것이 아닌 해당 user를 그대로 반환하시면 될 것 같습니당~

User updateUserInfo = userRepository.findByAccount(user.getAccount())
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));

return new VerifyResponse("인증이 성공적으로 완료되었습니다!",
new UserInfoDto(updateUserInfo.getAccount(), updateUserInfo.getEmail(), updateUserInfo.getGrade()));
}

}
Loading