Skip to content

Commit

Permalink
feat: 사용자 로그인 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Choi-JJunho committed Dec 16, 2023
1 parent ad06150 commit 61e703a
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 100 deletions.
6 changes: 1 addition & 5 deletions src/main/java/in/koreatech/koin/domain/user/Student.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import lombok.Getter;
Expand All @@ -17,9 +15,7 @@
public class Student {

@Id
@OneToOne(orphanRemoval = true)
@JoinColumn(name = "user_id")
private User user;
private Long userId;

@Size(max = 255)
@Column(name = "anonymous_nickname")
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/in/koreatech/koin/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.Instant;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -64,7 +64,7 @@ public class User extends BaseEntity {
private Boolean isAuthed = false;

@Column(name = "last_logged_at")
private Instant lastLoggedAt;
private LocalDateTime lastLoggedAt;

@Size(max = 255)
@Column(name = "profile_image_url")
Expand All @@ -91,9 +91,11 @@ public class User extends BaseEntity {
private String resetExpiredAt;

@Builder
private User(String password, String nickname, String name, String phoneNumber, UserType userType, String email,
UserGender gender, Boolean isAuthed, Instant lastLoggedAt, String profileImageUrl, Boolean isDeleted,
String authToken, String authExpiredAt, String resetToken, String resetExpiredAt) {
public User(String password, String nickname, String name, String phoneNumber, UserType userType,
String email,
UserGender gender, Boolean isAuthed, LocalDateTime lastLoggedAt, String profileImageUrl,
Boolean isDeleted,
String authToken, String authExpiredAt, String resetToken, String resetExpiredAt) {
this.password = password;
this.nickname = nickname;
this.name = name;
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/in/koreatech/koin/domain/user/UserToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package in.koreatech.koin.domain.user;

import java.util.concurrent.TimeUnit;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;

@Getter
@RedisHash("refreshToken")
public class UserToken {

@Id
private Long id;

private final String refreshToken;

@TimeToLive(unit = TimeUnit.DAYS)
private final Long expiration;

private UserToken(Long id, String refreshToken, Long expiration) {
this.id = id;
this.refreshToken = refreshToken;
this.expiration = expiration;

}

public static UserToken create(Long userId, String refreshToken) {
return new UserToken(userId, refreshToken, 3L);
}
}
1 change: 1 addition & 0 deletions src/main/java/in/koreatech/koin/dto/UserLoginRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class UserLoginRequest {
@Email(message = "이메일 형식을 지켜주세요.")
@NotBlank(message = "이메일을 입력해주세요.")
private String email;

@NotBlank(message = "비밀번호를 입력해주세요.")
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package in.koreatech.koin.repository;

import in.koreatech.koin.domain.user.UserToken;
import java.util.Optional;
import org.springframework.data.repository.Repository;

public interface UserTokenRepository extends Repository<UserToken, Long> {

UserToken save(UserToken userToken);

Optional<UserToken> findById(Long userId);
}
10 changes: 6 additions & 4 deletions src/main/java/in/koreatech/koin/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import in.koreatech.koin.auth.JwtProvider;
import in.koreatech.koin.domain.user.User;
import in.koreatech.koin.domain.user.UserToken;
import in.koreatech.koin.dto.UserLoginRequest;
import in.koreatech.koin.dto.UserLoginResponse;
import in.koreatech.koin.repository.UserRepository;
import in.koreatech.koin.repository.UserTokenRepository;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -15,8 +17,9 @@
@Transactional(readOnly = true)
public class UserService {

private final UserRepository userRepository;
private final JwtProvider jwtProvider;
private final UserRepository userRepository;
private final UserTokenRepository userTokenRepository;

@Transactional
public UserLoginResponse login(UserLoginRequest request) {
Expand All @@ -29,9 +32,8 @@ public UserLoginResponse login(UserLoginRequest request) {

String accessToken = jwtProvider.createToken(user);
String refreshToken = String.format("%s%d", UUID.randomUUID(), user.getId());
UserToken savedToken = userTokenRepository.save(UserToken.create(user.getId(), refreshToken));

// TODO: access, refresh token Redis에 저장
return UserLoginResponse.of(accessToken, "??", user.getUserType().getValue());
return UserLoginResponse.of(accessToken, savedToken.getRefreshToken(), user.getUserType().getValue());
}

}
2 changes: 1 addition & 1 deletion src/main/resources/application-example.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
jwt:
secret-key: example-secret-key
secret-key: EXAMPLE7A3E4F37B3DAD9CD8KEY6AA4B1AF7123!@#
access-token:
expiration-time: 600000

Expand Down
8 changes: 1 addition & 7 deletions src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
jwt:
secret-key: test-secret-key
secret-key: EXAMPLE7A3E4F37B3DAD9CD8KEY6AA4B1AF7123!@#
access-token:
expiration-time: 600000

Expand All @@ -12,12 +12,6 @@ spring:
hibernate:
ddl-auto: create


data:
redis:
port: 8888
host: localhost

logging:
level:
org:
Expand Down
21 changes: 15 additions & 6 deletions src/test/java/in/koreatech/koin/AcceptanceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.utility.DockerImageName;

@SpringBootTest(webEnvironment = RANDOM_PORT)
@Import(DBInitializer.class)
Expand All @@ -29,23 +30,31 @@ public abstract class AcceptanceTest {
@Autowired
private DBInitializer dataInitializer;

@Container
protected static MySQLContainer container;
protected static MySQLContainer mySqlContainer;
protected static GenericContainer<?> redisContainer;

@DynamicPropertySource
private static void configureProperties(final DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", container::getJdbcUrl);
registry.add("spring.datasource.url", mySqlContainer::getJdbcUrl);
registry.add("spring.datasource.username", () -> ROOT);
registry.add("spring.datasource.password", () -> ROOT_PASSWORD);
registry.add("spring.data.redis.host", redisContainer::getHost);
registry.add("spring.data.redis.port", () -> redisContainer.getMappedPort(6379).toString());
}

static {
container = (MySQLContainer) new MySQLContainer("mysql:5.7.34")
mySqlContainer = (MySQLContainer) new MySQLContainer("mysql:5.7.34")
.withDatabaseName("test")
.withUsername(ROOT)
.withPassword(ROOT_PASSWORD)
.withCommand("--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci");
container.start();

redisContainer = new GenericContainer<>(
DockerImageName.parse("redis:4.0.10"))
.withExposedPorts(6379);

mySqlContainer.start();
redisContainer.start();
}

@BeforeEach
Expand Down
123 changes: 51 additions & 72 deletions src/test/java/in/koreatech/koin/acceptance/UserApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,96 +2,75 @@


import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD;

import in.koreatech.koin.AcceptanceTest;
import in.koreatech.koin.domain.user.User;
import in.koreatech.koin.domain.user.UserToken;
import in.koreatech.koin.domain.user.UserType;
import in.koreatech.koin.repository.UserRepository;
//import in.koreatech.koin.repository.UserTokenRepository;
import in.koreatech.koin.repository.UserTokenRepository;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;

@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
class UserApiTest {

@LocalServerPort
int port;
class UserApiTest extends AcceptanceTest {

@Autowired
private UserRepository userRepository;

// @Autowired
// private UserTokenRepository tokenRepository;

private

@BeforeEach
void setUp() {
RestAssured.port = port;
}
@Autowired
private UserTokenRepository tokenRepository;

@Nested
@Test
@DisplayName("사용자가 로그인을 수행한다")
class userLogin {

@Test
@DisplayName("사용자가 로그인을 수행한다")
void userLoginSuccess() {
User user = User.builder()
.password("1234")
.nickname("주노")
.name("최준호")
.phoneNumber("010-1234-5678")
.userType(UserType.STUDENT)
.email("[email protected]")
.isAuthed(true)
.isDeleted(false)
.build();

userRepository.save(user);

ExtractableResponse<Response> response = RestAssured
.given()
.log().all()
.body("""
{
"email": "[email protected]",
"password": "1234"
}
""")
.when()
.log().all()
.post("/user/login")
.then()
.log().all()
.statusCode(HttpStatus.OK.value())
.extract();

User userResult = userRepository.findById(user.getId()).get();

assertSoftly(
softly -> {
softly.assertThat(response.jsonPath().getString("token")).isNotNull();
softly.assertThat(response.jsonPath().getString("refresh_token")).isNotNull();
softly.assertThat(response.jsonPath().getString("user_type")).isEqualTo("STUDENT");
softly.assertThat(userResult.getLastLoggedAt()).isNotNull();
void userLoginSuccess() {
User user = User.builder()
.password("1234")
.nickname("주노")
.name("최준호")
.phoneNumber("010-1234-5678")
.userType(UserType.STUDENT)
.email("[email protected]")
.isAuthed(true)
.isDeleted(false)
.build();

userRepository.save(user);

ExtractableResponse<Response> response = RestAssured
.given()
.log().all()
.body("""
{
"email": "[email protected]",
"password": "1234"
}
);
}

""")
.contentType(ContentType.JSON)
.when()
.log().all()
.post("/user/login")
.then()
.log().all()
.statusCode(HttpStatus.CREATED.value())
.extract();

User userResult = userRepository.findById(user.getId()).get();
UserToken token = tokenRepository.findById(user.getId()).get();

assertSoftly(
softly -> {
softly.assertThat(response.jsonPath().getString("token")).isNotNull();
softly.assertThat(response.jsonPath().getString("refresh_token")).isNotNull();
softly.assertThat(response.jsonPath().getString("refresh_token"))
.isEqualTo(token.getRefreshToken());
softly.assertThat(response.jsonPath().getString("user_type")).isEqualTo("STUDENT");
}
);
}


}

0 comments on commit 61e703a

Please sign in to comment.