diff --git a/src/main/java/nextstep/courses/domain/EnrolledStudent.java b/src/main/java/nextstep/courses/domain/EnrolledStudent.java new file mode 100644 index 0000000000..b5c2c76519 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/EnrolledStudent.java @@ -0,0 +1,42 @@ +package nextstep.courses.domain; + +import java.time.LocalDateTime; + +public class EnrolledStudent { + private Long id; + + private String userId; + + private Session session; + + private LocalDateTime enrolledAt; + + public EnrolledStudent(String userId, Session session, LocalDateTime enrolledAt) { + this.userId = userId; + this.session = session; + this.enrolledAt = enrolledAt; + } + + public EnrolledStudent(Long id, String userId, Session session, LocalDateTime enrolledAt) { + this.id = id; + this.userId = userId; + this.session = session; + this.enrolledAt = enrolledAt; + } + + public Long getId() { + return id; + } + + public String getUserId() { + return userId; + } + + public Session getSession() { + return session; + } + + public LocalDateTime getEnrolledAt() { + return enrolledAt; + } +} diff --git a/src/main/java/nextstep/courses/domain/EnrolledStudentRepository.java b/src/main/java/nextstep/courses/domain/EnrolledStudentRepository.java new file mode 100644 index 0000000000..13c568b22e --- /dev/null +++ b/src/main/java/nextstep/courses/domain/EnrolledStudentRepository.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain; + +public interface EnrolledStudentRepository { + void save(EnrolledStudent enrolledStudent); +} diff --git a/src/main/java/nextstep/courses/domain/EnrollmentHistory.java b/src/main/java/nextstep/courses/domain/EnrollmentHistory.java deleted file mode 100644 index bf37b5a3a0..0000000000 --- a/src/main/java/nextstep/courses/domain/EnrollmentHistory.java +++ /dev/null @@ -1,22 +0,0 @@ -package nextstep.courses.domain; - -import nextstep.users.domain.NsUser; - -import java.time.LocalDateTime; - -public class EnrollmentHistory { - private Long id; - - private NsUser user; - - private Session session; - - private LocalDateTime enrolledAt; - - - public EnrollmentHistory(NsUser user, Session session, LocalDateTime enrolledAt) { - this.user = user; - this.session = session; - this.enrolledAt = enrolledAt; - } -} diff --git a/src/main/java/nextstep/courses/domain/EnrollmentHistoryRepository.java b/src/main/java/nextstep/courses/domain/EnrollmentHistoryRepository.java deleted file mode 100644 index 1c57df9c06..0000000000 --- a/src/main/java/nextstep/courses/domain/EnrollmentHistoryRepository.java +++ /dev/null @@ -1,5 +0,0 @@ -package nextstep.courses.domain; - -public interface EnrollmentHistoryRepository { - void save(EnrollmentHistory enrollmentHistory); -} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index be77f14b4a..c94cb1111f 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -29,10 +29,10 @@ public class Session { private Long capacity; - private final List enrollments = new ArrayList<>(); + private final List enrolledStudents = new ArrayList<>(); - private Session(Long id, LocalDateTime createdAt, LocalDateTime updatedAt, CoverImage coverImage, SessionType sessionType, SessionStatus sessionStatus, Long price, Long capacity) { + public Session(Long id, LocalDateTime createdAt, LocalDateTime updatedAt, CoverImage coverImage, SessionType sessionType, SessionStatus sessionStatus, Long price, Long capacity) { this.id = id; this.createdAt = createdAt; this.updatedAt = updatedAt; @@ -76,13 +76,13 @@ public void open() { this.sessionStatus = SessionStatus.OPEN; } - public EnrollmentHistory enroll(NsUser user, Long amount, LocalDateTime enrolledAt) { + public EnrolledStudent enroll(NsUser user, Long amount, LocalDateTime enrolledAt) { validateSessionStatus(); validateSessionPrice(amount); validateSessionCapacity(); - EnrollmentHistory history = new EnrollmentHistory(user, this, enrolledAt); - enrollments.add(history); + EnrolledStudent history = new EnrolledStudent(user.getUserId(), this, enrolledAt); + enrolledStudents.add(history); return history; } @@ -109,6 +109,6 @@ public Long getId() { } private boolean isFull() { - return sessionType.isPaid() && enrollments.size() >= capacity; + return sessionType.isPaid() && enrolledStudents.size() >= capacity; } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcEnrolledStudentRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcEnrolledStudentRepository.java new file mode 100644 index 0000000000..189b79e1a9 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcEnrolledStudentRepository.java @@ -0,0 +1,27 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.EnrolledStudent; +import nextstep.courses.domain.EnrolledStudentRepository; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.stereotype.Repository; + +@Repository +public class JdbcEnrolledStudentRepository implements EnrolledStudentRepository { + + private final JdbcOperations jdbcTemplate; + + public JdbcEnrolledStudentRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void save(EnrolledStudent enrolledStudent) { + String sql = "INSERT INTO enrolled_student (session_id, user_id, enrolled_at) VALUES (?, ?, ?)"; + jdbcTemplate.update( + sql, + enrolledStudent.getSession().getId(), // session 객체에서 id 꺼냄 + enrolledStudent.getId(), + enrolledStudent.getEnrolledAt() + ); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java new file mode 100644 index 0000000000..d6567f6b38 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -0,0 +1,54 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.*; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +@Repository +public class JdbcSessionRepository implements SessionRepository { + + private final JdbcOperations jdbcTemplate; + + public JdbcSessionRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Session findById(Long sessionId) { + String sql = "SELECT id, created_at, updated_at, cover_image_size, cover_image_type, cover_image_width, cover_image_height, session_type, session_status, price, capacity FROM session WHERE id = ?"; + return jdbcTemplate.queryForObject(sql, sessionRowMapper(), sessionId); + } + + private RowMapper sessionRowMapper() { + return (rs, rowNum) -> { + CoverImage coverImage = new CoverImage( + rs.getInt("cover_image_size"), + ImageType.valueOf(rs.getString("cover_image_type")), + rs.getInt("cover_image_width"), + rs.getInt("cover_image_height") + ); + return new Session( + rs.getLong("id"), + toLocalDateTime(rs.getTimestamp("created_at")), + toLocalDateTime(rs.getTimestamp("updated_at")), + coverImage, + SessionType.valueOf(rs.getString("session_type")), + SessionStatus.valueOf(rs.getString("session_status")), + rs.getLong("price"), + rs.getObject("capacity", Long.class) + ); + }; + } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } + +} diff --git a/src/main/java/nextstep/courses/service/EnrollmentService.java b/src/main/java/nextstep/courses/service/EnrollmentService.java index a4c8f9b65c..1158b51053 100644 --- a/src/main/java/nextstep/courses/service/EnrollmentService.java +++ b/src/main/java/nextstep/courses/service/EnrollmentService.java @@ -1,8 +1,8 @@ package nextstep.courses.service; import nextstep.common.exception.UserNotFoundException; -import nextstep.courses.domain.EnrollmentHistory; -import nextstep.courses.domain.EnrollmentHistoryRepository; +import nextstep.courses.domain.EnrolledStudent; +import nextstep.courses.domain.EnrolledStudentRepository; import nextstep.courses.domain.Session; import nextstep.courses.domain.SessionRepository; import nextstep.payments.domain.Payment; @@ -17,13 +17,13 @@ public class EnrollmentService { private final SessionRepository sessionRepository; private final UserRepository userRepository; private final PaymentRepository paymentRepository; - private final EnrollmentHistoryRepository enrollmentHistoryRepository; + private final EnrolledStudentRepository enrolledStudentRepository; - public EnrollmentService(SessionRepository sessionRepository, UserRepository userRepository, PaymentRepository paymentRepository, EnrollmentHistoryRepository enrollmentHistoryRepository) { + public EnrollmentService(SessionRepository sessionRepository, UserRepository userRepository, PaymentRepository paymentRepository, EnrolledStudentRepository enrolledStudentRepository) { this.sessionRepository = sessionRepository; this.userRepository = userRepository; this.paymentRepository = paymentRepository; - this.enrollmentHistoryRepository = enrollmentHistoryRepository; + this.enrolledStudentRepository = enrolledStudentRepository; } public void enroll(Long sessionId, String userId, Long paymentId) { @@ -31,8 +31,8 @@ public void enroll(Long sessionId, String userId, Long paymentId) { NsUser user = userRepository.findByUserId(userId) .orElseThrow(UserNotFoundException::new); Payment payment = paymentRepository.findById(paymentId); - EnrollmentHistory enrollmentHistory = session.enroll(user, payment.getAmount(), LocalDateTime.now()); - enrollmentHistoryRepository.save(enrollmentHistory); + EnrolledStudent enrolledStudent = session.enroll(user, payment.getAmount(), LocalDateTime.now()); + enrolledStudentRepository.save(enrolledStudent); } } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 4a6bc5a665..057dc67f9e 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,10 +1,28 @@ -INSERT INTO ns_user (id, user_id, password, name, email, created_at) values (1, 'javajigi', 'test', '자바지기', 'javajigi@slipp.net', CURRENT_TIMESTAMP()); -INSERT INTO ns_user (id, user_id, password, name, email, created_at) values (2, 'sanjigi', 'test', '산지기', 'sanjigi@slipp.net', CURRENT_TIMESTAMP()); +INSERT INTO ns_user (id, user_id, password, name, email, created_at) +values (1, 'javajigi', 'test', '자바지기', 'javajigi@slipp.net', CURRENT_TIMESTAMP()); +INSERT INTO ns_user (id, user_id, password, name, email, created_at) +values (2, 'sanjigi', 'test', '산지기', 'sanjigi@slipp.net', CURRENT_TIMESTAMP()); -INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUES (1, 1, '국내에서 Ruby on Rails와 Play가 활성화되기 힘든 이유는 뭘까?', 'Ruby on Rails(이하 RoR)는 2006년 즈음에 정말 뜨겁게 달아올랐다가 금방 가라 앉았다. Play 프레임워크는 정말 한 순간 잠시 눈에 뜨이다가 사라져 버렸다. RoR과 Play 기반으로 개발을 해보면 정말 생산성이 높으며, 웹 프로그래밍이 재미있기까지 하다. Spring MVC + JPA(Hibernate) 기반으로 진행하면 설정할 부분도 많고, 기본으로 지원하지 않는 기능도 많아 RoR과 Play에서 기본적으로 지원하는 기능을 서비스하려면 추가적인 개발이 필요하다.', CURRENT_TIMESTAMP(), false); +INSERT INTO question (id, writer_id, title, contents, created_at, deleted) +VALUES (1, 1, '국내에서 Ruby on Rails와 Play가 활성화되기 힘든 이유는 뭘까?', + 'Ruby on Rails(이하 RoR)는 2006년 즈음에 정말 뜨겁게 달아올랐다가 금방 가라 앉았다. Play 프레임워크는 정말 한 순간 잠시 눈에 뜨이다가 사라져 버렸다. RoR과 Play 기반으로 개발을 해보면 정말 생산성이 높으며, 웹 프로그래밍이 재미있기까지 하다. Spring MVC + JPA(Hibernate) 기반으로 진행하면 설정할 부분도 많고, 기본으로 지원하지 않는 기능도 많아 RoR과 Play에서 기본적으로 지원하는 기능을 서비스하려면 추가적인 개발이 필요하다.', + CURRENT_TIMESTAMP(), false); -INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) VALUES (1, 'http://underscorejs.org/docs/underscore.html Underscore.js 강추합니다! 쓸일도 많고, 코드도 길지 않고, 자바스크립트의 언어나 기본 API를 보완하는 기능들이라 자바스크립트 이해에 도움이 됩니다. 무엇보다 라이브러리 자체가 아주 유용합니다.', CURRENT_TIMESTAMP(), 1, false); +INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) +VALUES (1, + 'http://underscorejs.org/docs/underscore.html Underscore.js 강추합니다! 쓸일도 많고, 코드도 길지 않고, 자바스크립트의 언어나 기본 API를 보완하는 기능들이라 자바스크립트 이해에 도움이 됩니다. 무엇보다 라이브러리 자체가 아주 유용합니다.', + CURRENT_TIMESTAMP(), 1, false); -INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) VALUES (2, '언더스코어 강력 추천드려요. 다만 최신 버전을 공부하는 것보다는 0.10.0 버전부터 보는게 더 좋더군요. 코드의 변천사도 알 수 있고, 최적화되지 않은 코드들이 기능은 그대로 두고 최적화되어 가는 걸 보면 재미가 있습니다 :)', CURRENT_TIMESTAMP(), 1, false); +INSERT INTO answer (writer_id, contents, created_at, question_id, deleted) +VALUES (2, + '언더스코어 강력 추천드려요. 다만 최신 버전을 공부하는 것보다는 0.10.0 버전부터 보는게 더 좋더군요. 코드의 변천사도 알 수 있고, 최적화되지 않은 코드들이 기능은 그대로 두고 최적화되어 가는 걸 보면 재미가 있습니다 :)', + CURRENT_TIMESTAMP(), 1, false); -INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUES (2, 2, 'runtime 에 reflect 발동 주체 객체가 뭔지 알 방법이 있을까요?', '설계를 희한하게 하는 바람에 꼬인 문제같긴 합니다만. 여쭙습니다. 상황은 mybatis select 실행될 시에 return object 의 getter 가 호출되면서인데요. getter 안에 다른 property 에 의존중인 코드가 삽입되어 있어서, 만약 다른 mybatis select 구문에 해당 property 가 없다면 exception 이 발생하게 됩니다.', CURRENT_TIMESTAMP(), false); +INSERT INTO question (id, writer_id, title, contents, created_at, deleted) +VALUES (2, 2, 'runtime 에 reflect 발동 주체 객체가 뭔지 알 방법이 있을까요?', + '설계를 희한하게 하는 바람에 꼬인 문제같긴 합니다만. 여쭙습니다. 상황은 mybatis select 실행될 시에 return object 의 getter 가 호출되면서인데요. getter 안에 다른 property 에 의존중인 코드가 삽입되어 있어서, 만약 다른 mybatis select 구문에 해당 property 가 없다면 exception 이 발생하게 됩니다.', + CURRENT_TIMESTAMP(), false); + +insert into session (id, created_at, updated_at, cover_image_size, cover_image_type, cover_image_width, + cover_image_height, session_type, session_status, price, capacity) +values (1, now(), now(), 500000, 'JPG', 450, 300, 'PAID', 'OPEN', 10000, 100); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 8d5a988c8b..4834de35d5 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,50 +1,80 @@ -create table course ( - id bigint generated by default as identity, - title varchar(255) not null, - creator_id bigint not null, - created_at timestamp not null, +create table course +( + id bigint generated by default as identity, + title varchar(255) not null, + creator_id bigint not null, + created_at timestamp not null, updated_at timestamp, primary key (id) ); -create table ns_user ( - id bigint generated by default as identity, - user_id varchar(20) not null, - password varchar(20) not null, - name varchar(20) not null, - email varchar(50), - created_at timestamp not null, +create table ns_user +( + id bigint generated by default as identity, + user_id varchar(20) not null, + password varchar(20) not null, + name varchar(20) not null, + email varchar(50), + created_at timestamp not null, updated_at timestamp, primary key (id) ); -create table question ( - id bigint generated by default as identity, - created_at timestamp not null, +create table question +( + id bigint generated by default as identity, + created_at timestamp not null, updated_at timestamp, contents clob, - deleted boolean not null, - title varchar(100) not null, - writer_id bigint, + deleted boolean not null, + title varchar(100) not null, + writer_id bigint, primary key (id) ); -create table answer ( - id bigint generated by default as identity, - created_at timestamp not null, - updated_at timestamp, +create table answer +( + id bigint generated by default as identity, + created_at timestamp not null, + updated_at timestamp, contents clob, - deleted boolean not null, + deleted boolean not null, question_id bigint, - writer_id bigint, + writer_id bigint, primary key (id) ); -create table delete_history ( - id bigint not null, - content_id bigint, - content_type varchar(255), - created_date timestamp, +create table delete_history +( + id bigint not null, + content_id bigint, + content_type varchar(255), + created_date timestamp, deleted_by_id bigint, primary key (id) ); + +create table session +( + id bigint generated by default as identity, + created_at timestamp not null, + updated_at timestamp, + cover_image_size int not null, + cover_image_type varchar(20) not null, + cover_image_width int not null, + cover_image_height int not null, + session_type varchar(20) not null, + session_status varchar(20) not null, + price bigint not null, + capacity bigint, + primary key (id) +); + +create table enrolled_student +( + id bigint generated by default as identity, + session_id bigint not null, + user_id varchar(20) not null, + enrolled_at timestamp not null, + primary key (id) +); diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index 1b23e8a628..fec6b4597b 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -23,13 +23,13 @@ void paidSessionEnrollTest() { LocalDateTime now = LocalDateTime.now(); //when - EnrollmentHistory enroll = session.enroll(user, payment.getAmount(), now); + EnrolledStudent enroll = session.enroll(user, payment.getAmount(), now); //then Assertions.assertThat(enroll) .isNotNull() - .extracting("user", "session", "enrolledAt") - .containsExactly(user, session, now); + .extracting("userId", "session", "enrolledAt") + .containsExactly(user.getUserId(), session, now); } @Test diff --git a/src/test/java/nextstep/courses/infrastructure/JdbcEnrolledStudentRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/JdbcEnrolledStudentRepositoryTest.java new file mode 100644 index 0000000000..0afc9e63f0 --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/JdbcEnrolledStudentRepositoryTest.java @@ -0,0 +1,54 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.*; +import nextstep.users.domain.NsUser; +import nextstep.users.domain.NsUserTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.time.LocalDateTime; + +@JdbcTest +class JdbcEnrolledStudentRepositoryTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + private EnrolledStudentRepository enrolledStudentRepository; + private SessionRepository sessionRepository; + + @BeforeEach + void setUp() { + enrolledStudentRepository = new JdbcEnrolledStudentRepository(jdbcTemplate); + sessionRepository = new JdbcSessionRepository(jdbcTemplate); + } + + @Test + @DisplayName("EnrolledStudent를 저장한다") + void save() { + // given + Session session = sessionRepository.findById(1L); + NsUser user = NsUserTest.JAVAJIGI; + + EnrolledStudent enrolledStudent = new EnrolledStudent(1L, user.getUserId(), session, LocalDateTime.now()); + + // when + enrolledStudentRepository.save(enrolledStudent); + + // then + Long count = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM enrolled_student WHERE session_id = ? AND user_id = ?", + Long.class, + session.getId(), + user.getId() + ); + + Assertions.assertThat(count).isEqualTo(1L); + } + +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java new file mode 100644 index 0000000000..e0bf218880 --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -0,0 +1,45 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.CoverImage; +import nextstep.courses.domain.Session; +import nextstep.courses.domain.SessionRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.jdbc.core.JdbcTemplate; + +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +public class SessionRepositoryTest { + + + @Autowired + private JdbcTemplate jdbcTemplate; + + private SessionRepository sessionRepository; + + @BeforeEach + void setUp() { + sessionRepository = new JdbcSessionRepository(jdbcTemplate); + } + + @Test + @DisplayName("Session을 ID로 조회한다") + void findById() { + // given + Long sessionId = 1L; + + // when + Session session = sessionRepository.findById(sessionId); + + // then + Assertions.assertThat(session) + .isNotNull() + .extracting(Session::getId) + .isEqualTo(sessionId); + } +} \ No newline at end of file