-
Notifications
You must be signed in to change notification settings - Fork 1
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
[hoony-lab-issue12] User 도메인 구현 #19
Changes from 27 commits
7905b5a
9ebe365
ea16e7b
6713f70
f8fda88
6c474b6
214377d
d958304
d2817b8
48e6cf0
0623bb6
e43fb1f
e60bde9
f77ffb7
6ce2d98
3612e99
74abc86
9a6f361
91cddf0
496fc01
02df674
fa4bffc
e96f72a
61940b0
35d86b6
5ce0037
812e27f
cec6f69
c68c196
86bf551
51e51e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.study.realworld.config; | ||
|
||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; | ||
|
||
@EnableJpaAuditing | ||
@Configuration | ||
public class JpaConfig {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.study.realworld.config; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
|
||
@EnableWebSecurity | ||
public class SecurityConfig extends WebSecurityConfigurerAdapter { | ||
|
||
@Override | ||
protected void configure(HttpSecurity http) throws Exception { | ||
http | ||
.csrf().disable() | ||
.headers().frameOptions().disable() | ||
.and() | ||
.authorizeRequests() | ||
.antMatchers("/api/hello", "/h2-console/**").permitAll() | ||
.antMatchers("/api/users/**", "/api/users/login/**").permitAll() | ||
.antMatchers("/api/articles/**").permitAll() | ||
.antMatchers("/api/tags/**").permitAll() | ||
.antMatchers("/error").permitAll() | ||
.anyRequest().authenticated(); | ||
} | ||
|
||
@Bean | ||
public PasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.study.realworld.global.common; | ||
|
||
import lombok.Getter; | ||
import org.springframework.data.annotation.CreatedDate; | ||
import org.springframework.data.annotation.LastModifiedDate; | ||
import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
||
import javax.persistence.Column; | ||
import javax.persistence.EntityListeners; | ||
import javax.persistence.MappedSuperclass; | ||
import java.time.LocalDateTime; | ||
|
||
@Getter | ||
@MappedSuperclass | ||
@EntityListeners(AuditingEntityListener.class) | ||
public abstract class BaseTimeEntity { | ||
|
||
@CreatedDate | ||
@Column(name = "created_at", updatable = false) | ||
private LocalDateTime createdAt; | ||
|
||
@LastModifiedDate | ||
@Column(name = "updated_at") | ||
private LocalDateTime updatedAt; | ||
|
||
@Column(name = "deleted_at") | ||
private LocalDateTime deletedAt; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package com.study.realworld.user.domain; | ||
|
||
import com.study.realworld.global.common.BaseTimeEntity; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
|
||
import javax.persistence.*; | ||
import java.util.Objects; | ||
|
||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Entity | ||
public class User extends BaseTimeEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
@Column(nullable = false, updatable = false) | ||
private Long id; | ||
|
||
@Column(nullable = false, length = 20) | ||
private String email; | ||
|
||
@Column(nullable = false, length = 50) | ||
private String username; | ||
|
||
@Column(nullable = false) | ||
private String password; | ||
|
||
@Column | ||
private String bio; | ||
|
||
@Column | ||
private String image; | ||
|
||
@Builder | ||
public User(Long id, String email, String username, String password, String bio, String image) { | ||
this.id = id; | ||
this.email = email; | ||
this.username = username; | ||
this.password = password; | ||
this.bio = bio; | ||
this.image = image; | ||
} | ||
|
||
public static User of(User user) { | ||
return User.builder() | ||
.email(user.getEmail()) | ||
.username(user.getUsername()) | ||
.password(user.getPassword()) | ||
.bio(user.getBio()) | ||
.image(user.getImage()) | ||
.build(); | ||
} | ||
Comment on lines
+47
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 어디에 사용되는 메소드일까용? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금은 안쓰이네요 ㅠ |
||
|
||
public void encodePassword(PasswordEncoder passwordEncoder) { | ||
this.password = passwordEncoder.encode(password); | ||
} | ||
|
||
public boolean matchesPassword(String rawPassword, PasswordEncoder passwordEncoder) { | ||
return passwordEncoder.matches(rawPassword, this.password); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
User user = (User) o; | ||
return Objects.equals(email, user.email); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(email); | ||
} | ||
Comment on lines
+66
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프록시 객체 활용시 값이 null 이 나올 수 있어서 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.study.realworld.user.domain; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.Optional; | ||
|
||
public interface UserRepository extends JpaRepository<User, Long> { | ||
Optional<User> findByEmail(String email); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.study.realworld.user.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
import com.fasterxml.jackson.annotation.JsonTypeName; | ||
import com.study.realworld.user.domain.User; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.WRAPPER_OBJECT; | ||
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME; | ||
|
||
@JsonTypeName("user") | ||
@JsonTypeInfo(include = WRAPPER_OBJECT, use = NAME) | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
public class UserJoinRequest { | ||
|
||
private String username; | ||
private String email; | ||
private String password; | ||
|
||
@Builder | ||
public UserJoinRequest(String username, String email, String password) { | ||
this.username = username; | ||
this.email = email; | ||
this.password = password; | ||
} | ||
|
||
public static User toUser(UserJoinRequest request) { | ||
return User.builder() | ||
.username(request.getUsername()) | ||
.email(request.getEmail()) | ||
.password(request.getPassword()) | ||
.build(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.study.realworld.user.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
import com.fasterxml.jackson.annotation.JsonTypeName; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.WRAPPER_OBJECT; | ||
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME; | ||
|
||
@JsonTypeName("user") | ||
@JsonTypeInfo(include = WRAPPER_OBJECT, use = NAME) | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
public class UserLoginRequest { | ||
|
||
private String email; | ||
private String password; | ||
|
||
@Builder | ||
public UserLoginRequest(String email, String password) { | ||
this.email = email; | ||
this.password = password; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.study.realworld.user.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
import com.fasterxml.jackson.annotation.JsonTypeName; | ||
import com.study.realworld.user.domain.User; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.WRAPPER_OBJECT; | ||
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME; | ||
|
||
@JsonTypeName("user") | ||
@JsonTypeInfo(include = WRAPPER_OBJECT, use = NAME) | ||
@Getter | ||
public class UserResponse { | ||
|
||
private final String email; | ||
private final String token; | ||
private final String username; | ||
private final String bio; | ||
private final String image; | ||
|
||
@Builder | ||
public UserResponse(String email, String token, String username, String bio, String image) { | ||
this.email = email; | ||
this.token = token; | ||
this.username = username; | ||
this.bio = bio; | ||
this.image = image; | ||
} | ||
|
||
public static UserResponse of(User user, String token) { | ||
return UserResponse.builder() | ||
.email(user.getEmail()) | ||
.token(token) | ||
.username(user.getUsername()) | ||
.bio(user.getBio()) | ||
.image(user.getImage()) | ||
.build(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package com.study.realworld.user.service; | ||
|
||
import com.study.realworld.user.domain.User; | ||
import com.study.realworld.user.domain.UserRepository; | ||
import com.study.realworld.user.dto.UserJoinRequest; | ||
import com.study.realworld.user.dto.UserLoginRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.dao.DuplicateKeyException; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
import java.util.NoSuchElementException; | ||
|
||
@RequiredArgsConstructor | ||
@Transactional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
@Service | ||
public class UserService { | ||
|
||
private final PasswordEncoder passwordEncoder; | ||
private final UserRepository userRepository; | ||
|
||
@Transactional(readOnly = true) | ||
public List<User> findAll() { | ||
return userRepository.findAll(); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public User findById(Long id) { | ||
return userRepository.findById(id) | ||
.orElseThrow(() -> new NoSuchElementException(id + " not found")); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public User findByEmail(String email) { | ||
return userRepository.findByEmail(email) | ||
.orElseThrow(() -> new NoSuchElementException(email + " not found")); | ||
} | ||
|
||
public User save(UserJoinRequest request) { | ||
userRepository.findByEmail(request.getEmail()) | ||
.ifPresent(o -> new DuplicateKeyException(o.getEmail() + " already exist")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런식으로 익셉션을 사용하는거군요..! 너무 좋은 코드같아요 많이 배웁니다!!!... 👍 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
User user = UserJoinRequest.toUser(request); | ||
user.encodePassword(passwordEncoder); | ||
|
||
return userRepository.save(user); | ||
} | ||
|
||
public User deleteById(Long id) { | ||
User user = userRepository.findById(id) | ||
.orElseThrow(() -> new NoSuchElementException(id + " not found")); | ||
|
||
userRepository.delete(user); | ||
return user; | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public User login(UserLoginRequest request) { | ||
User user = userRepository.findByEmail(request.getEmail()) | ||
.orElseThrow(() -> new NoSuchElementException(request.getEmail() + " not found")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. findByEmail이 중복되는 것 같은데 메서드로 분리해서 재사용도 좋을 것 같네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
if(!user.matchesPassword(request.getPassword(), passwordEncoder)) { | ||
throw new NoSuchElementException(request.getPassword() + " wrong wrong wrong triple wrong" + user.getPassword()); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 에러 검증 로직이 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
참고할수있는 키워드 하나만 주시면 더 공부해보겠습니다 ! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사람마다 기준점은 다르지만 저 같은 경우 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 에러 발생을 도메인단으로 보내도 될 것 같은데 이 부분도 고려해볼만 하겠네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 감사합니다 뇌섹 우지님 💘 |
||
|
||
return user; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GenerationType.IDENTITY
의 경우 id를 제외한 빌더를 사용해도 상관없으니 참고 부탁해요 :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 맞습니다
DB쪽(?) 에서 케어하는 부분이라 상관없나보군요 🆗
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위에꺼는 왜 코멘트를 달 수 있는 부분이 안보일까요 ㅠㅠ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이전에 코멘트 달았는데 outdate 되어서 그런것 같아요 ㅎㅎ