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 : Owner 회원가입 & 로그인 구현 #60

Merged
merged 26 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ed8ada9
feat : Owner 로그인을 위한 email & Password 필드 추가
kkangh00n Jan 4, 2024
28c3c45
feat : Owner 회원가입 dto
kkangh00n Jan 4, 2024
2179ae0
feat : PasswordEncoder
kkangh00n Jan 4, 2024
ae90cfb
fix : 저장 시 encoding된 Password 저장
kkangh00n Jan 4, 2024
76678a9
feat : 성별 입력에 대한 타입 검증
kkangh00n Jan 4, 2024
15849da
feat : Owner 회원가입 로직 구현
kkangh00n Jan 4, 2024
5b67aea
feat : Owner 회원가입 API
kkangh00n Jan 4, 2024
22cfd9b
feat : UserDetails 상속 & role 필드 추가
kkangh00n Jan 4, 2024
eed4958
feat : Owner 로그인 로직 구현
kkangh00n Jan 4, 2024
44c78af
refactor : 메서드 이름 변경
kkangh00n Jan 4, 2024
2fe4fb5
feat : Owner 로그인 API
kkangh00n Jan 4, 2024
135068d
refactor : 검증 로직 메서드 분리
kkangh00n Jan 4, 2024
a0f3690
fix : 비밀번호 검증 논리 오류 수정
kkangh00n Jan 5, 2024
885b63d
feat : dto builder 추가
kkangh00n Jan 5, 2024
32a9584
feat : 테스트 용 데이터 생성
kkangh00n Jan 5, 2024
7ea8395
feat : OwnerService Test
kkangh00n Jan 5, 2024
7b1923e
fix : 가입 시 전화번호 입력 패턴 수정
kkangh00n Jan 5, 2024
e030fcc
fix : LocalDateTime을 Json으로 변환하기 위한 모듈 추가
kkangh00n Jan 5, 2024
86e1a5a
feat : OwnerController Test
kkangh00n Jan 5, 2024
80aca16
style : 코드 포맷팅
kkangh00n Jan 5, 2024
4d234f9
fix : conflict resolve
kkangh00n Jan 5, 2024
bd3b603
fix : 잘못된 문법 사용 수정
kkangh00n Jan 5, 2024
845a497
fix : 접근 제어자 수정
kkangh00n Jan 7, 2024
a6d3196
refactor : dev merge
kkangh00n Jan 7, 2024
2a8801b
fix : conflict resolve
kkangh00n Jan 7, 2024
4b756c2
fix : conflict resolve
kkangh00n Jan 7, 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ public enum ErrorCode {
CAN_NOT_COMPLETE_WAITING("입장 처리가 불가한 대기 상태입니다."),
EXISTING_MEMBER_WAITING("이미 회원이 웨이팅 중인 가게가 존재합니다."),
SHOP_NOT_RUNNING("가게가 영업시간이 아닙니다."),
INTERNAL_SERVER_ERROR("내부 서버 오류입니다.");
INTERNAL_SERVER_ERROR("내부 서버 오류입니다."),

ALREADY_EXIST_OWNER("이미 존재하는 점주입니다"),
BAD_REQUEST_EMAIL_OR_PASSWORD("이메일 혹은 비밀번호를 확인해주세요"),
BAD_REQUEST_INPUT_GENDER_TYPE("성별 타입을 양식대로 입력해주세요");

Copy link
Member

Choose a reason for hiding this comment

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

BadRequestCustomException으로 감싸주니까 INVALID_EMAIL_OR_PASSWORD 이렇게 네이밍해도 괜찮지 않을까 생각이 들었습니다!

private final String message;
}
26 changes: 24 additions & 2 deletions src/main/java/com/prgrms/catchtable/member/domain/Gender.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
package com.prgrms.catchtable.member.domain;

import static com.prgrms.catchtable.common.exception.ErrorCode.BAD_REQUEST_INPUT_GENDER_TYPE;

import com.prgrms.catchtable.common.exception.custom.BadRequestCustomException;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum Gender {
MALE,
FEMALE
MALE("male"),
FEMALE("female");

private final String type;

public static Gender of(String input) {
return Arrays.stream(values())
.filter(gender -> gender.isEqual(input))
.findAny()
.orElseThrow(() -> new BadRequestCustomException(BAD_REQUEST_INPUT_GENDER_TYPE));
}
Copy link
Member

Choose a reason for hiding this comment

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

request에서 검증하지 않고 Enum에서 input을 검증하면 캡슐화의 장점이 있는건지 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Enum으로의 변환로직? 이라고 생각할 때, request dto보다는 Enum 안에서 따로 로직이 있어야 된다고 생각하였습니다..!


private boolean isEqual(String input) {
return input.equals(this.type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.prgrms.catchtable.owner.controller;

import com.prgrms.catchtable.jwt.token.Token;
import com.prgrms.catchtable.owner.dto.request.JoinOwnerRequest;
import com.prgrms.catchtable.owner.dto.request.LoginOwnerRequest;
import com.prgrms.catchtable.owner.dto.response.JoinOwnerResponse;
import com.prgrms.catchtable.owner.service.OwnerService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;

@RestController
@RequiredArgsConstructor
@RequestMapping("/owners")
public class OwnerController {

private final OwnerService ownerService;

@PostMapping("/join")
public ResponseEntity<JoinOwnerResponse> join(
@Valid @RequestBody JoinOwnerRequest joinOwnerRequest) {
JoinOwnerResponse joinOwnerResponse = ownerService.joinOwner(joinOwnerRequest);

return ResponseEntity.status(HttpStatus.CREATED).body(joinOwnerResponse);
}

@PostMapping("/login")
public ResponseEntity<Token> login(@Valid @RequestBody LoginOwnerRequest loginOwnerRequest) {
Token responseToken = ownerService.loginOwner(loginOwnerRequest);

return ResponseEntity.ok(responseToken);
}

}
54 changes: 52 additions & 2 deletions src/main/java/com/prgrms/catchtable/owner/domain/Owner.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static lombok.AccessLevel.PROTECTED;

import com.prgrms.catchtable.common.BaseEntity;
import com.prgrms.catchtable.common.Role;
import com.prgrms.catchtable.member.domain.Gender;
import com.prgrms.catchtable.shop.domain.Shop;
import jakarta.persistence.Column;
Expand All @@ -18,14 +19,19 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Collections;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Getter
@NoArgsConstructor(access = PROTECTED)
@Entity
public class Owner extends BaseEntity {
public class Owner extends BaseEntity implements UserDetails {

@Id
@GeneratedValue(strategy = IDENTITY)
Expand All @@ -35,13 +41,23 @@ public class Owner extends BaseEntity {
@Column(name = "owner_name")
private String name;

@Column(name = "email")
private String email;

@Column(name = "password")
private String password;

@Column(name = "phone_number")
private String phoneNumber;

@Column(name = "gender")
@Enumerated(STRING)
private Gender gender;

@Column(name = "role")
@Enumerated(STRING)
private Role role;

@Column(name = "date_birth")
private LocalDate dateBirth;

Expand All @@ -50,10 +66,44 @@ public class Owner extends BaseEntity {
private Shop shop;

@Builder
public Owner(String name, String phoneNumber, Gender gender, LocalDate dateBirth) {
public Owner(String name, String email, String password, String phoneNumber, Gender gender,
LocalDate dateBirth) {
this.name = name;
this.email = email;
this.password = password;
this.phoneNumber = phoneNumber;
this.gender = gender;
this.dateBirth = dateBirth;
this.role = Role.OWNER;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority(role.getRole()));
}

@Override
public String getUsername() {
return email;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/prgrms/catchtable/owner/dto/OwnerMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.prgrms.catchtable.owner.dto;

import com.prgrms.catchtable.member.domain.Gender;
import com.prgrms.catchtable.owner.domain.Owner;
import com.prgrms.catchtable.owner.dto.request.JoinOwnerRequest;
import com.prgrms.catchtable.owner.dto.response.JoinOwnerResponse;

public class OwnerMapper {

public static Owner toEntity(JoinOwnerRequest joinOwnerRequest, String encodePassword,
Gender gender) {
return Owner.builder()
.name(joinOwnerRequest.name())
.email(joinOwnerRequest.email())
.password(encodePassword)
.phoneNumber(joinOwnerRequest.phoneNumber())
.gender(gender)
.dateBirth(joinOwnerRequest.dateBirth())
.build();
}

public static JoinOwnerResponse from(Owner owner) {
return JoinOwnerResponse.builder()
.name(owner.getName())
.email(owner.getEmail())
.phoneNumber(owner.getPhoneNumber())
.gender(owner.getGender().getType())
.dateBirth(owner.getDateBirth())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.prgrms.catchtable.owner.dto.request;

import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Pattern;
import java.time.LocalDate;
import lombok.Builder;

@Builder
public record JoinOwnerRequest(

String name,
@Email
Copy link
Member

Choose a reason for hiding this comment

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

message 옵션으로 검증 메세지를 추가해줘도 좋을 것 같아요!

String email,
String password,
@Pattern(regexp = "^(01[016789]){1}-([0-9]{3,4}){1}-([0-9]{4}){1}$")
String phoneNumber,
String gender,
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate dateBirth
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.prgrms.catchtable.owner.dto.request;

import jakarta.validation.constraints.Email;
import lombok.Builder;

@Builder
public record LoginOwnerRequest(

@Email
String email,
String password

) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.prgrms.catchtable.owner.dto.response;

import java.time.LocalDate;
import lombok.Builder;

@Builder
public record JoinOwnerResponse(

String name,
String email,
String phoneNumber,
String gender,
LocalDate dateBirth
) {

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.prgrms.catchtable.owner.repository;

import com.prgrms.catchtable.owner.domain.Owner;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OwnerRepository extends JpaRepository<Owner, Long> {

boolean existsOwnerByEmail(String email);

Optional<Owner> findOwnerByEmail(String email);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.prgrms.catchtable.owner.service;

import static com.prgrms.catchtable.common.exception.ErrorCode.ALREADY_EXIST_OWNER;
import static com.prgrms.catchtable.common.exception.ErrorCode.BAD_REQUEST_EMAIL_OR_PASSWORD;

import com.prgrms.catchtable.common.exception.custom.BadRequestCustomException;
import com.prgrms.catchtable.jwt.provider.JwtTokenProvider;
import com.prgrms.catchtable.jwt.token.Token;
import com.prgrms.catchtable.member.domain.Gender;
import com.prgrms.catchtable.owner.domain.Owner;
import com.prgrms.catchtable.owner.dto.OwnerMapper;
import com.prgrms.catchtable.owner.dto.request.JoinOwnerRequest;
import com.prgrms.catchtable.owner.dto.request.LoginOwnerRequest;
import com.prgrms.catchtable.owner.dto.response.JoinOwnerResponse;
import com.prgrms.catchtable.owner.repository.OwnerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OwnerService {

private final OwnerRepository ownerRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;

public JoinOwnerResponse joinOwner(JoinOwnerRequest joinOwnerRequest) {

//이미 존재하는 이메일이라면
validateExistsOwner(joinOwnerRequest);

String encodePassword = passwordEncoder.encode(joinOwnerRequest.password());

Gender gender = Gender.of(joinOwnerRequest.gender());

Owner joinOwner = ownerRepository.save(
OwnerMapper.toEntity(joinOwnerRequest, encodePassword, gender));

return OwnerMapper.from(joinOwner);

}

private void validateExistsOwner(JoinOwnerRequest joinOwnerRequest) {
if (ownerRepository.existsOwnerByEmail(joinOwnerRequest.email())) {
throw new BadRequestCustomException(ALREADY_EXIST_OWNER);
}
}

public Token loginOwner(LoginOwnerRequest loginRequest) {

//email 확인
Owner loginOwner = ownerRepository.findOwnerByEmail(loginRequest.email())
.orElseThrow(() -> new BadRequestCustomException(BAD_REQUEST_EMAIL_OR_PASSWORD));

//password 확인
validatePassword(loginRequest, loginOwner);

return jwtTokenProvider.createToken(loginOwner.getEmail());
}

private void validatePassword(LoginOwnerRequest loginRequest, Owner loginOwner) {
if (!passwordEncoder.matches(loginRequest.password(), loginOwner.getPassword())) {
throw new BadRequestCustomException(BAD_REQUEST_EMAIL_OR_PASSWORD);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public CreateReservationResponse preOccupyReservation(CreateReservationRequest r

@Transactional
public CreateReservationResponse registerReservation(CreateReservationRequest request) {
ReservationTime reservationTime = reservationTimeRepository.findByIdWithShop( //예약시간과 매장 한번에 가져옴
ReservationTime reservationTime = reservationTimeRepository.findByIdWithShop(
request.reservationTimeId()).
orElseThrow(() -> new NotFoundCustomException(NOT_EXIST_TIME));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

@Configuration
@RequiredArgsConstructor
Expand All @@ -24,6 +25,11 @@ public class SecurityConfig {
private final ExceptionHandlerFilter exceptionHandlerFilter;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public class TestController {

@GetMapping("/testMember")
public ResponseEntity<String> testMember(){
public ResponseEntity<String> testMember() {
log.info("testMember");
return ResponseEntity.ok("testMember OK");
}
Expand Down
Loading