Skip to content

Commit

Permalink
Merge pull request #19 from real-world-study/hoony-lab-issue12
Browse files Browse the repository at this point in the history
[hoony-lab-issue12] User 도메인 구현
  • Loading branch information
hoony-lab committed Aug 13, 2021
2 parents f7b58eb + 51e51e6 commit b5adfca
Show file tree
Hide file tree
Showing 19 changed files with 922 additions and 11 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: build

on:
push:
branches: [ main, chance0523, com8599, DolphaGo, hoony-lab, jinyoungchoi95, JunHo-YH, povia, sujl95 , kwj1270 ]
branches: [ main, chance0523, com8599, DolphaGo, hoony-lab, jinyoungchoi95, povia , kwj1270 , ERrorASER ]
pull_request:
branches: [ main, chance0523, com8599, DolphaGo, hoony-lab, jinyoungchoi95, JunHo-YH, povia, sujl95 , kwj1270 ]
branches: [ main, chance0523, com8599, DolphaGo, hoony-lab, jinyoungchoi95, povia , kwj1270 , ERrorASER ]

jobs:
build:
Expand Down
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/study/realworld/config/JpaConfig.java
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 {}
34 changes: 34 additions & 0 deletions src/main/java/com/study/realworld/config/SecurityConfig.java
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;

}
78 changes: 78 additions & 0 deletions src/main/java/com/study/realworld/user/domain/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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(String email, String username, String password, String bio, String image) {
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();
}

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);
}

}
10 changes: 10 additions & 0 deletions src/main/java/com/study/realworld/user/domain/UserRepository.java
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);

}
39 changes: 39 additions & 0 deletions src/main/java/com/study/realworld/user/dto/UserJoinRequest.java
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();
}

}
28 changes: 28 additions & 0 deletions src/main/java/com/study/realworld/user/dto/UserLoginRequest.java
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;
}

}
42 changes: 42 additions & 0 deletions src/main/java/com/study/realworld/user/dto/UserResponse.java
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();
}

}
70 changes: 70 additions & 0 deletions src/main/java/com/study/realworld/user/service/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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
@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) {
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 = this.findByEmail(request.getEmail());
validateMatchesPassword(user, request.getPassword());

return user;
}

private void validateMatchesPassword(User user, String rawPassword) {
if(!user.matchesPassword(rawPassword, passwordEncoder)) {
throw new NoSuchElementException(rawPassword + " wrong wrong wrong triple wrong" + user.getPassword());
}
}

}

0 comments on commit b5adfca

Please sign in to comment.