From cd795cf6d990abbc421b85ad3d4be93c8b589bda Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Tue, 12 Dec 2023 22:32:18 +0900 Subject: [PATCH 1/8] =?UTF-8?q?refactor:=20=EC=88=98=EA=B0=95=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=A0=80=EC=9E=A5=20=EC=8B=9C=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EB=94=94=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JdbcStudentsRepository.java | 30 ++++++++++++++++--- .../repository/StudentsRepository.java | 2 +- .../java/nextstep/users/domain/NsUser.java | 5 ++-- .../StudentsRepositoryTest.java | 11 ++++--- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java index 147c958cb..eecc3c403 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java @@ -5,8 +5,12 @@ import nextstep.users.domain.NsUser; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import java.sql.PreparedStatement; +import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.Set; @@ -20,22 +24,40 @@ public JdbcStudentsRepository(JdbcOperations jdbcTemplate) { } @Override - public int save(Long id, Long sessionId) { + public Long save(Long id, Long sessionId) { String sql = "insert into students (session_id, user_id, created_at) values (?, ?, ?)"; - return jdbcTemplate.update(sql, sessionId, id, LocalDateTime.now()); + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, sessionId); + ps.setLong(2, id); + ps.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now())); + return ps; + }, keyHolder); + + return keyHolder.getKey().longValue(); } @Override public Students findBySessionId(Long id) { - String sql = "select ns.id, ns.user_id, ns.password, ns.name, ns.email from students st inner join ns_user ns where st.user_id = ns.id and st.session_id = ?"; + String sql = "select ns.id, ns.user_id, ns.password, ns.name, ns.email, ns.created_at, ns.updated_at from students st inner join ns_user ns where st.user_id = ns.id and st.session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new NsUser( rs.getLong(1), rs.getString(2), rs.getString(3), rs.getString(4), - rs.getString(5) + rs.getString(5), + toLocalDateTime(rs.getTimestamp(6)), + toLocalDateTime(rs.getTimestamp(7)) ); return new Students(Set.of(jdbcTemplate.queryForObject(sql, rowMapper, id))); } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } } diff --git a/src/main/java/nextstep/courses/repository/StudentsRepository.java b/src/main/java/nextstep/courses/repository/StudentsRepository.java index c05268630..77d5edd63 100644 --- a/src/main/java/nextstep/courses/repository/StudentsRepository.java +++ b/src/main/java/nextstep/courses/repository/StudentsRepository.java @@ -4,7 +4,7 @@ public interface StudentsRepository { - int save(Long id, Long sessionId); + Long save(Long id, Long sessionId); Students findBySessionId(Long id); } diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index 25050fee3..91c4d8289 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -124,14 +124,13 @@ public boolean isGuestUser() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; NsUser nsUser = (NsUser) o; - return Objects.equals(id, nsUser.id) && Objects.equals(userId, nsUser.userId) && Objects.equals(password, nsUser.password) && Objects.equals(name, nsUser.name) && Objects.equals(email, nsUser.email); + return id != null & Objects.equals(id, nsUser.id); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), id, userId, password, name, email); + return id != null ? id.intValue() : 0; } @Override diff --git a/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java index ccd17d206..f3f5b6ed6 100644 --- a/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java @@ -39,13 +39,12 @@ void crud() { Session session = sessionRepository.findById(3L); session.openSession(); - session.register(Payment.ofPaid(1L, 1L, nsUser, 20_000L)); - int count = studentsRepository.save(nsUser.getId(), 2L); - assertThat(count).isEqualTo(1); + session.register(Payment.ofPaid(1L, 3L, nsUser, 20_000L)); + Long id = studentsRepository.save(nsUser.getId(), 3L); + assertThat(id).isEqualTo(1L); - Students students = studentsRepository.findBySessionId(2L); - System.out.println(students); - assertThat(students.isContains(nsUser)).isTrue(); //왜 false인지 모르겠음 ㅠㅠ + Students students = studentsRepository.findBySessionId(3L); + assertThat(students.isContains(nsUser)).isTrue(); LOGGER.debug("Students: {}", students); } } From 8396e29201a528064327f8f24631e9a178473acb Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Wed, 13 Dec 2023 00:48:49 +0900 Subject: [PATCH 2/8] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- md/step4.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 md/step4.md diff --git a/md/step4.md b/md/step4.md new file mode 100644 index 000000000..5613eb2bb --- /dev/null +++ b/md/step4.md @@ -0,0 +1,29 @@ +# 🚀 4단계 - 수강신청(요구사항 변경) + +## 핵심 학습 목표 + +- DB 테이블이 변경될 때도 스트랭글러 패턴을 적용해 점진적인 리팩터링을 연습한다. + - [스트랭글러(교살자) 패턴 - 마틴 파울러](https://martinfowler.com/bliki/StranglerFigApplication.html) + - [스트랭글러 무화과 패턴](https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig) + +
+ +## 변경된 기능 요구사항 +### **강의 수강신청은 강의 상태가 모집중일 때만 가능하다.** +- [ ] 강의가 진행 중인 상태에서도 수강신청이 가능해야 한다. + - 강의 진행 상태(준비중, 진행중, 종료)와 모집 상태(비모집중, 모집중)로 상태 값을 분리해야 한다. + +### **강의는 강의 커버 이미지 정보를 가진다.** +- [ ] 강의는 **하나 이상**의 커버 이미지를 가질 수 있다. + +### 강사가 승인하지 않아도 수강 신청하는 모든 사람이 수강 가능하다. +- [ ] 우아한테크코스(무료), 우아한테크캠프 Pro(유료)와 같이 선발된 인원만 수강 가능해야 한다. + - [ ] 강사는 수강신청한 사람 중 선발된 인원에 대해서만 수강 승인이 가능해야 한다. + - [ ] 강사는 수강신청한 사람 중 선발되지 않은 사람은 수강을 취소할 수 있어야 한다. + +
+ +## 프로그래밍 요구사항 +- 리팩터링할 때 컴파일 에러와 기존의 단위 테스트의 실패를 최소화하면서 점진적인 리팩터링이 가능하도록 한다. +- DB 테이블에 데이터가 존재한다는 가정하에 리팩터링해야 한다. + - 즉, 기존에 쌓인 데이터를 제거하지 않은 상태로 리팩터링 해야 한다. From d8942f15f9ed5f69825e44c186974df9f8cdf560 Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Wed, 13 Dec 2023 22:54:49 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EA=B0=95=EC=9D=98=EA=B0=80=20?= =?UTF-8?q?=ED=95=98=EB=82=98=20=EC=9D=B4=EC=83=81=EC=9D=98=20=EC=BB=A4?= =?UTF-8?q?=EB=B2=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A7=88=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/courses/domain/CoverImages.java | 21 +++++++++++++ .../java/nextstep/courses/domain/Session.java | 30 ++++++++----------- .../exception/EmptyCoverImageException.java | 8 +++++ .../JdbcCoverImageRepository.java | 13 +++++++- .../infrastructure/JdbcSessionRepository.java | 26 ++++++++-------- .../repository/CoverImageRepository.java | 4 +++ src/main/resources/data.sql | 2 ++ src/main/resources/schema.sql | 15 ++++++++++ .../nextstep/courses/domain/CourseTest.java | 3 +- .../courses/domain/CoverImagesTest.java | 5 ++++ .../nextstep/courses/domain/SessionTest.java | 24 ++++++++++----- .../CoverImageRepositoryTest.java | 9 ++++++ .../infrastructure/SessionRepositoryTest.java | 13 ++++---- 13 files changed, 126 insertions(+), 47 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/CoverImages.java create mode 100644 src/main/java/nextstep/courses/exception/EmptyCoverImageException.java create mode 100644 src/test/java/nextstep/courses/domain/CoverImagesTest.java diff --git a/src/main/java/nextstep/courses/domain/CoverImages.java b/src/main/java/nextstep/courses/domain/CoverImages.java new file mode 100644 index 000000000..969834ad1 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/CoverImages.java @@ -0,0 +1,21 @@ +package nextstep.courses.domain; + +import nextstep.courses.exception.EmptyCoverImageException; + +import java.util.List; + +public class CoverImages { + + private List images; + + public CoverImages(List images) { + validateIsNull(images); + this.images = images; + } + + private void validateIsNull(List images) { + if (images == null || images.isEmpty()) { + throw new EmptyCoverImageException(); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index 2a12b369b..d1f9b2705 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -13,39 +13,39 @@ public class Session extends BaseEntity { private final Long id; private final Long courseId; private final SessionType type; - private final CoverImage coverImage; + private final CoverImages coverImages; private final Period period; private Status status; private final Students students; private final PaidCondition paidCondition; - public static Session ofFree(Long id, Long courseId, CoverImage coverImage, LocalDate startDate, LocalDate endDate) { - return new Session(id, courseId, SessionType.FREE, coverImage, new Period(startDate, endDate), Status.NOT_OPEN, 0, 0L, LocalDateTime.now(), null); + public static Session ofFree(Long id, Long courseId, CoverImages coverImages, LocalDate startDate, LocalDate endDate) { + return new Session(id, courseId, SessionType.FREE, coverImages, new Period(startDate, endDate), Status.NOT_OPEN, 0, 0L, LocalDateTime.now(), null); } - public static Session ofPaid(Long id, Long courseId, CoverImage coverImage, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee) { - return new Session(id, courseId, SessionType.PAID, coverImage, new Period(startDate, endDate), Status.NOT_OPEN, maxStudents, fee, LocalDateTime.now(), null); + public static Session ofPaid(Long id, Long courseId, CoverImages coverImages, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee) { + return new Session(id, courseId, SessionType.PAID, coverImages, new Period(startDate, endDate), Status.NOT_OPEN, maxStudents, fee, LocalDateTime.now(), null); } - public static Session of(Long id, Long courseId, SessionType type, CoverImage coverImage, Status status, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { - return new Session(id, courseId, type, coverImage, new Period(startDate, endDate), status, maxStudents, fee, createdAt, updatedAt); + public static Session of(Long id, Long courseId, SessionType type, CoverImages coverImages, Status status, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { + return new Session(id, courseId, type, coverImages, new Period(startDate, endDate), status, maxStudents, fee, createdAt, updatedAt); } - private Session(Long id, Long courseId, SessionType type, CoverImage coverImage, Period period, Status status, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { + private Session(Long id, Long courseId, SessionType type, CoverImages coverImages, Period period, Status status, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { super(createdAt, updatedAt); - validateNotNull(id, coverImage, period); + validateNotNull(id, coverImages, period); this.id = id; this.courseId = courseId; this.type = type; - this.coverImage = coverImage; + this.coverImages = coverImages; this.period = period; this.status = status; this.students = new Students(); this.paidCondition = new PaidCondition(maxStudents, fee); } - private void validateNotNull(Long id, CoverImage coverImage, Period period) { - if (id == null || coverImage == null || period == null) { + private void validateNotNull(Long id, CoverImages coverImages, Period period) { + if (id == null || coverImages == null || period == null) { throw new InvalidSessionException(); } } @@ -83,10 +83,6 @@ public Long courseId() { return courseId; } - public Long imageId() { - return coverImage.getId(); - } - public String type() { return type.name(); } @@ -116,7 +112,7 @@ public String toString() { return "Session{" + "id=" + id + ", type=" + type + - ", coverImage=" + coverImage + + ", coverImages=" + coverImages + ", period=" + period + ", status=" + status + ", students=" + students + diff --git a/src/main/java/nextstep/courses/exception/EmptyCoverImageException.java b/src/main/java/nextstep/courses/exception/EmptyCoverImageException.java new file mode 100644 index 000000000..49d5a79b8 --- /dev/null +++ b/src/main/java/nextstep/courses/exception/EmptyCoverImageException.java @@ -0,0 +1,8 @@ +package nextstep.courses.exception; + +public class EmptyCoverImageException extends RuntimeException { + + public EmptyCoverImageException() { + super("강의는 하나 이상의 커버 이미지를 가져야 합니다."); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java index 3210c0491..9f42c1333 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java @@ -8,6 +8,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.List; @Repository("coverImageRepository") public class JdbcCoverImageRepository implements CoverImageRepository { @@ -27,6 +28,16 @@ public int save(CoverImage image) { @Override public CoverImage findById(Long id) { String sql = "select id, size, extension, width, height, created_at, updated_at from cover_image where id = ?"; + return jdbcTemplate.queryForObject(sql, rowMapper(), id); + } + + @Override + public List findAllBySessionId(Long id) { + String sql = "select id, size, extension, width, height, created_at, updated_at from cover_image where session_id = ?"; + return jdbcTemplate.query(sql, rowMapper(), id); + } + + private RowMapper rowMapper() { RowMapper rowMapper = (rs, rowNum) -> new CoverImage( rs.getLong(1), rs.getLong(2), @@ -35,7 +46,7 @@ public CoverImage findById(Long id) { rs.getInt(5), toLocalDateTime(rs.getTimestamp(6)), toLocalDateTime(rs.getTimestamp(7))); - return jdbcTemplate.queryForObject(sql, rowMapper, id); + return rowMapper; } private LocalDateTime toLocalDateTime(Timestamp timestamp) { diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 755df2281..d21151901 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,6 +1,6 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.CoverImage; +import nextstep.courses.domain.CoverImages; import nextstep.courses.domain.Session; import nextstep.courses.domain.SessionType; import nextstep.courses.domain.Status; @@ -29,25 +29,25 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public int save(Session session) { - String sql = "insert into session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, session.id(), session.courseId(), session.imageId(), session.type(), session.status(), session.startDate(), session.endDate(), session.maxStudents(), session.fee(), session.getCreatedAt()); + String sql = "insert into session2 (id, course_id, type, status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, session.id(), session.courseId(), session.type(), session.status(), session.startDate(), session.endDate(), session.maxStudents(), session.fee(), session.getCreatedAt()); } @Override public Session findById(Long id) { - String sql = "select id, course_id, type, image_id, status, start_date, end_date, max_students, fee, created_at, updated_at from session where id = ?"; + String sql = "select id, course_id, type, status, start_date, end_date, max_students, fee, created_at, updated_at from session where id = ?"; RowMapper rowMapper = (rs, rowNum) -> Session.of( rs.getLong(1), rs.getLong(2), SessionType.findByCode(rs.getString(3)), - coverImage(rs.getLong(4)), - Status.findByName(rs.getString(5)), + coverImages(id), + Status.findByName(rs.getString(4)), + toLocalDate(rs.getDate(5)), toLocalDate(rs.getDate(6)), - toLocalDate(rs.getDate(7)), - rs.getInt(8), - rs.getLong(9), - toLocalDateTime(rs.getTimestamp(10)), - toLocalDateTime(rs.getTimestamp(11))); + rs.getInt(7), + rs.getLong(8), + toLocalDateTime(rs.getTimestamp(9)), + toLocalDateTime(rs.getTimestamp(10))); return jdbcTemplate.queryForObject(sql, rowMapper, id); } @@ -66,7 +66,7 @@ private LocalDate toLocalDate(Date date) { return date.toLocalDate(); } - private CoverImage coverImage(Long id) { - return coverImageRepository.findById(id); + private CoverImages coverImages(Long id) { + return new CoverImages(coverImageRepository.findAllBySessionId(id)); } } diff --git a/src/main/java/nextstep/courses/repository/CoverImageRepository.java b/src/main/java/nextstep/courses/repository/CoverImageRepository.java index 44948543c..15e6b4651 100644 --- a/src/main/java/nextstep/courses/repository/CoverImageRepository.java +++ b/src/main/java/nextstep/courses/repository/CoverImageRepository.java @@ -2,8 +2,12 @@ import nextstep.courses.domain.CoverImage; +import java.util.List; + public interface CoverImageRepository { int save(CoverImage coverImage); CoverImage findById(Long id); + + List findAllBySessionId(Long id); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 0d92f4161..4263b4c6d 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -12,6 +12,8 @@ INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUE INSERT INTO course (id, title, creator_id, created_at) VALUES (2, 'JPA 활용편1', 2, CURRENT_TIMESTAMP()); INSERT INTO cover_image (id, size, extension, width, height, created_at) VALUES (2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); +INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (3, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); +INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (4, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 2, 'FREE', 'NOT_OPEN', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 2, 'PAID', 'NOT_OPEN', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 4e0d8e4fb..ccc6d8356 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -51,6 +51,7 @@ create table delete_history ( create table cover_image ( id bigint not null, + session_id bigint, size double not null, extension varchar(10) not null, width int not null, @@ -75,6 +76,20 @@ create table session ( primary key (id) ); +create table session2 ( + id bigint not null, + course_id bigint not null, + type varchar(10) not null, + status varchar(10) not null, + start_date timestamp not null, + end_date timestamp not null, + max_students int, + fee int, + created_at timestamp not null, + updated_at timestamp, + primary key (id) +); + create table students ( id bigint generated by default as identity, session_id bigint not null, diff --git a/src/test/java/nextstep/courses/domain/CourseTest.java b/src/test/java/nextstep/courses/domain/CourseTest.java index 24460c689..d6f5c34ed 100644 --- a/src/test/java/nextstep/courses/domain/CourseTest.java +++ b/src/test/java/nextstep/courses/domain/CourseTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import java.time.LocalDate; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -15,7 +16,7 @@ void add_session() { Course course = new Course(0L, "TDD, 클린 코드 with Java 17기", 1L); CoverImage coverImage = new CoverImage(1, "jpg", 300, 200); LocalDate now = LocalDate.now(); - Session session = Session.ofFree(0L, 1L, coverImage, now, now); + Session session = Session.ofFree(0L, 1L, new CoverImages(List.of(coverImage)), now, now); course.addSession(session); diff --git a/src/test/java/nextstep/courses/domain/CoverImagesTest.java b/src/test/java/nextstep/courses/domain/CoverImagesTest.java new file mode 100644 index 000000000..b45044bd2 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/CoverImagesTest.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain; + +class CoverImagesTest { + +} diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index f2b8fa35c..8194194f0 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import java.time.LocalDate; +import java.util.List; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -18,7 +19,7 @@ class SessionTest { @Test @DisplayName("무료 강의 수강 신청 시 강의 상태가 모집중이 아니면 예외를 던진다.") void register_status_check() { - Session freeSession = Session.ofFree(1L, 2L, coverImage(), START_DATE, END_DATE); + Session freeSession = Session.ofFree(1L, 2L, coverImages(), START_DATE, END_DATE); assertThatThrownBy(() -> freeSession.register(Payment.ofFree(1L, NsUserTest.JAVAJIGI))) .isInstanceOf(NotOpenSessionException.class); } @@ -26,7 +27,7 @@ void register_status_check() { @Test @DisplayName("강의 상태를 모집중으로 변경 시 현재 날짜가 강의 기간에 속하지 않으면 예외를 던진다.") void register_open() { - Session session = Session.ofFree(1L, 2L, coverImage(), START_DATE, LocalDate.of(2023, 12, 2)); + Session session = Session.ofFree(1L, 2L, coverImages(), START_DATE, LocalDate.of(2023, 12, 2)); assertThatThrownBy(() -> session.openSession()) .isInstanceOf(OutOfSessionException.class); } @@ -34,7 +35,7 @@ void register_open() { @Test @DisplayName("유료 강의 수강 신청 시 최대 수강 인원을 초과하면 예외를 던진다.") void register_over_students() { - Session paidSession = Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, 1, 10_000L); + Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); paidSession.openSession(); paidSession.register(Payment.ofPaid(1L, 1L, NsUserTest.JAVAJIGI, 10_000L)); @@ -46,7 +47,7 @@ void register_over_students() { @Test @DisplayName("유료 강의 수강 신청 시 결제금액과 수강료가 일치하는지 확인하지 않으면 예외를 던진다.") void session_fee_test() { - Session paidSession = Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, 1, 10_000L); + Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); paidSession.openSession(); assertThatThrownBy(() -> paidSession.register(Payment.ofPaid(2L, 1L, NsUserTest.SANJIGI, 8_000L))) @@ -56,14 +57,14 @@ void session_fee_test() { @Test @DisplayName("강의 생성 시 결제금액과 수강료가 음수면 예외를 던진다.") void session_paid_condition_null() { - assertThatThrownBy(() -> Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, -1, -1L)) + assertThatThrownBy(() -> Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, -1, -1L)) .isInstanceOf(NegativePaidConditionException.class); } @Test @DisplayName("중복 수강 신청 시 예외를 던진다.") void duplicate_register() { - Session paidSession = Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, 2, 10_000L); + Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 2, 10_000L); paidSession.openSession(); paidSession.register(Payment.ofPaid(1L, 1L, NsUserTest.SANJIGI, 10_000L)); @@ -72,7 +73,14 @@ void duplicate_register() { .isInstanceOf(DuplicateStudentsException.class); } - private static CoverImage coverImage() { - return new CoverImage(1, "gif", 300, 200); + @Test + @DisplayName("강의 생성 시 이미지가 없으면 예외를 던진다.") + void empty_images() { + assertThatThrownBy(() -> Session.ofPaid(1L, 2L, new CoverImages(null), START_DATE, END_DATE, 1, 10_000L)) + .isInstanceOf(EmptyCoverImageException.class); + } + + private static CoverImages coverImages() { + return new CoverImages(List.of(new CoverImage(1, "gif", 300, 200))); } } diff --git a/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java index c3d3e8cac..238a67344 100644 --- a/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java @@ -10,6 +10,8 @@ import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.jdbc.core.JdbcTemplate; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; @JdbcTest @@ -41,4 +43,11 @@ void crud() { void find() { assertThat(coverImageRepository.findById(2L).getId()).isEqualTo(2L); } + + @Test + void findAllBySessionId() { + List images = coverImageRepository.findAllBySessionId(2L); + assertThat(images.get(0).getId()).isEqualTo(3L); + assertThat(images.get(1).getId()).isEqualTo(4L); + } } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 9372e44ad..18fd14828 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -1,8 +1,8 @@ package nextstep.courses.infrastructure; import nextstep.courses.domain.CoverImage; +import nextstep.courses.domain.CoverImages; import nextstep.courses.domain.Session; -import nextstep.courses.repository.CoverImageRepository; import nextstep.courses.repository.SessionRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,6 +13,7 @@ import org.springframework.jdbc.core.JdbcOperations; import java.time.LocalDate; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -26,17 +27,15 @@ class SessionRepositoryTest { private JdbcOperations jdbcTemplates; private SessionRepository sessionRepository; - private CoverImageRepository coverImageRepository; @BeforeEach void setUp() { sessionRepository = new JdbcSessionRepository(jdbcTemplates); - coverImageRepository = new JdbcCoverImageRepository(jdbcTemplates); } @Test void free_crud() { - Session session = Session.ofFree(1L, 2L, coverImage(), LocalDate.now(), LocalDate.of(2023, 12, 31)); + Session session = Session.ofFree(1L, 2L, coverImages(), LocalDate.now(), LocalDate.of(2023, 12, 31)); int count = sessionRepository.save(session); assertThat(count).isEqualTo(1); @@ -47,7 +46,7 @@ void free_crud() { @Test void paid_crud() { - Session session = Session.ofPaid(1L, 2L, coverImage(), LocalDate.now(), LocalDate.of(2023, 12, 31), 10, 10_000L); + Session session = Session.ofPaid(1L, 2L, coverImages(), LocalDate.now(), LocalDate.of(2023, 12, 31), 10, 10_000L); int count = sessionRepository.save(session); assertThat(count).isEqualTo(1); @@ -60,7 +59,7 @@ void paid_crud() { LOGGER.debug("Session: {}", paidSession); } - private CoverImage coverImage() { - return coverImageRepository.findById(2L); + private CoverImages coverImages() { + return new CoverImages(List.of(new CoverImage(1, "jpg", 300, 200))); } } From 3bc847f1a239ed6f21675de0fa60066c3dd755f7 Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Wed, 13 Dec 2023 23:05:03 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=EA=B0=95=EC=9D=98=EA=B0=80=20?= =?UTF-8?q?=ED=95=98=EB=82=98=20=EC=9D=B4=EC=83=81=EC=9D=98=20=EC=BB=A4?= =?UTF-8?q?=EB=B2=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A7=88=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20(as-is=20->=20to-be)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JdbcCoverImageRepository.java | 20 +++++++++---------- .../repository/CoverImageRepository.java | 2 +- .../courses/service/CoverImageService.java | 18 +++++++++++++++++ src/main/resources/data.sql | 2 +- src/main/resources/schema.sql | 2 +- .../CoverImageRepositoryTest.java | 2 +- 6 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 src/main/java/nextstep/courses/service/CoverImageService.java diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java index 9f42c1333..aaa84a932 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java @@ -20,32 +20,32 @@ public JdbcCoverImageRepository(JdbcOperations jdbcTemplate) { } @Override - public int save(CoverImage image) { - String sql = "insert into cover_image (id, size, extension, width, height, created_at) values(?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, image.getId(), image.getSize(), image.getExtension(), image.getWidth(), image.getHeight(), image.getCreatedAt()); + public int save(CoverImage image, Long sessionId) { + String sql = "insert into cover_image (id, session_id, size, extension, width, height, created_at) values(?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, image.getId(), sessionId, image.getSize(), image.getExtension(), image.getWidth(), image.getHeight(), image.getCreatedAt()); } @Override public CoverImage findById(Long id) { - String sql = "select id, size, extension, width, height, created_at, updated_at from cover_image where id = ?"; + String sql = "select id, session_id, size, extension, width, height, created_at, updated_at from cover_image where id = ?"; return jdbcTemplate.queryForObject(sql, rowMapper(), id); } @Override public List findAllBySessionId(Long id) { - String sql = "select id, size, extension, width, height, created_at, updated_at from cover_image where session_id = ?"; + String sql = "select id, session_id, size, extension, width, height, created_at, updated_at from cover_image where session_id = ?"; return jdbcTemplate.query(sql, rowMapper(), id); } private RowMapper rowMapper() { RowMapper rowMapper = (rs, rowNum) -> new CoverImage( rs.getLong(1), - rs.getLong(2), - rs.getString(3), - rs.getInt(4), + rs.getLong(3), + rs.getString(4), rs.getInt(5), - toLocalDateTime(rs.getTimestamp(6)), - toLocalDateTime(rs.getTimestamp(7))); + rs.getInt(6), + toLocalDateTime(rs.getTimestamp(7)), + toLocalDateTime(rs.getTimestamp(8))); return rowMapper; } diff --git a/src/main/java/nextstep/courses/repository/CoverImageRepository.java b/src/main/java/nextstep/courses/repository/CoverImageRepository.java index 15e6b4651..7473f5c99 100644 --- a/src/main/java/nextstep/courses/repository/CoverImageRepository.java +++ b/src/main/java/nextstep/courses/repository/CoverImageRepository.java @@ -5,7 +5,7 @@ import java.util.List; public interface CoverImageRepository { - int save(CoverImage coverImage); + int save(CoverImage coverImage, Long sessionId); CoverImage findById(Long id); diff --git a/src/main/java/nextstep/courses/service/CoverImageService.java b/src/main/java/nextstep/courses/service/CoverImageService.java new file mode 100644 index 000000000..31488b613 --- /dev/null +++ b/src/main/java/nextstep/courses/service/CoverImageService.java @@ -0,0 +1,18 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.CoverImage; +import nextstep.courses.repository.CoverImageRepository; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; + +public class CoverImageService { + + @Resource(name = "coverImageRepository") + private CoverImageRepository coverImageRepository; + + @Transactional + public void createCoverImage(CoverImage image, Long sessionId) { + coverImageRepository.save(image, sessionId); + } +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 4263b4c6d..a7feda8df 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -11,7 +11,7 @@ INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUE INSERT INTO course (id, title, creator_id, created_at) VALUES (2, 'JPA 활용편1', 2, CURRENT_TIMESTAMP()); -INSERT INTO cover_image (id, size, extension, width, height, created_at) VALUES (2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); +INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (2, 3, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (3, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (4, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index ccc6d8356..d8a72fa90 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -51,7 +51,7 @@ create table delete_history ( create table cover_image ( id bigint not null, - session_id bigint, + session_id bigint not null, size double not null, extension varchar(10) not null, width int not null, diff --git a/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java index 238a67344..4589f3f51 100644 --- a/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java @@ -32,7 +32,7 @@ void setUp() { @Test void crud() { CoverImage image = new CoverImage(1, "jpg", 300, 200); - int count = coverImageRepository.save(image); + int count = coverImageRepository.save(image, 2L); assertThat(count).isEqualTo(1); CoverImage savedImage = coverImageRepository.findById(1L); assertThat(savedImage.getId()).isEqualTo(image.getId()); From 5750dc23045e9893db2a1ee67ae4f47f80b31f99 Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Thu, 14 Dec 2023 01:56:47 +0900 Subject: [PATCH 5/8] =?UTF-8?q?remove:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=EC=95=8A=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/nextstep/courses/domain/CoverImagesTest.java | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/test/java/nextstep/courses/domain/CoverImagesTest.java diff --git a/src/test/java/nextstep/courses/domain/CoverImagesTest.java b/src/test/java/nextstep/courses/domain/CoverImagesTest.java deleted file mode 100644 index b45044bd2..000000000 --- a/src/test/java/nextstep/courses/domain/CoverImagesTest.java +++ /dev/null @@ -1,5 +0,0 @@ -package nextstep.courses.domain; - -class CoverImagesTest { - -} From 8e77d9d15737b6ca3737564516fca8cdbb3fdaa7 Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Thu, 14 Dec 2023 01:58:36 +0900 Subject: [PATCH 6/8] =?UTF-8?q?remove:=20=EA=B0=95=EC=9D=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EA=B0=80=20=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=BC?= =?UTF-8?q?=EB=95=8C=EB=8F=84=20=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/courses/domain/Period.java | 4 -- .../courses/domain/RecruitmentStatus.java | 27 ++++++++++ .../java/nextstep/courses/domain/Session.java | 36 ++++++------- .../courses/domain/SessionStatus.java | 25 +++++++++ .../java/nextstep/courses/domain/Status.java | 45 ++++++++++------ .../InvalidRecruitmentStatusException.java | 8 +++ .../InvalidSessionStatusException.java | 8 --- .../exception/NotOpenSessionException.java | 2 +- .../infrastructure/JdbcSessionRepository.java | 10 ++-- src/main/resources/data.sql | 2 + src/main/resources/schema.sql | 2 +- .../courses/domain/SessionStatusTest.java | 24 +++++++++ .../nextstep/courses/domain/SessionTest.java | 54 ++++++++++++------- .../StudentsRepositoryTest.java | 2 +- 14 files changed, 174 insertions(+), 75 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/RecruitmentStatus.java create mode 100644 src/main/java/nextstep/courses/domain/SessionStatus.java create mode 100644 src/main/java/nextstep/courses/exception/InvalidRecruitmentStatusException.java delete mode 100644 src/main/java/nextstep/courses/exception/InvalidSessionStatusException.java create mode 100644 src/test/java/nextstep/courses/domain/SessionStatusTest.java diff --git a/src/main/java/nextstep/courses/domain/Period.java b/src/main/java/nextstep/courses/domain/Period.java index f57b036fb..b5a4d9606 100644 --- a/src/main/java/nextstep/courses/domain/Period.java +++ b/src/main/java/nextstep/courses/domain/Period.java @@ -25,10 +25,6 @@ private void validatePeriod(LocalDate startDate, LocalDate endDate) { } } - public boolean isDateWithinRange(LocalDate date) { - return !date.isBefore(startDate) && !date.isAfter(endDate); - } - public LocalDate startDate() { return startDate; } diff --git a/src/main/java/nextstep/courses/domain/RecruitmentStatus.java b/src/main/java/nextstep/courses/domain/RecruitmentStatus.java new file mode 100644 index 000000000..c39f26c24 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/RecruitmentStatus.java @@ -0,0 +1,27 @@ +package nextstep.courses.domain; + +import nextstep.courses.exception.InvalidRecruitmentStatusException; + +import java.util.Arrays; + +public enum RecruitmentStatus { + NOT_RECRUITMENT, + RECRUITING; + + private static final RecruitmentStatus[] VALUES = values(); + + public static RecruitmentStatus findByName(String name) { + return Arrays.stream(VALUES) + .filter(status -> status.name().equalsIgnoreCase(name)) + .findFirst() + .orElseThrow(() -> new InvalidRecruitmentStatusException(name)); + } + + public RecruitmentStatus ofRecruiting() { + return RECRUITING; + } + + public boolean isRecruiting() { + return this == RECRUITING; + } +} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index d1f9b2705..69e826883 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -1,8 +1,6 @@ package nextstep.courses.domain; import nextstep.courses.exception.InvalidSessionException; -import nextstep.courses.exception.NotOpenSessionException; -import nextstep.courses.exception.OutOfSessionException; import nextstep.payments.domain.Payment; import java.time.LocalDate; @@ -15,20 +13,20 @@ public class Session extends BaseEntity { private final SessionType type; private final CoverImages coverImages; private final Period period; - private Status status; + private final Status status; private final Students students; private final PaidCondition paidCondition; public static Session ofFree(Long id, Long courseId, CoverImages coverImages, LocalDate startDate, LocalDate endDate) { - return new Session(id, courseId, SessionType.FREE, coverImages, new Period(startDate, endDate), Status.NOT_OPEN, 0, 0L, LocalDateTime.now(), null); + return new Session(id, courseId, SessionType.FREE, coverImages, new Period(startDate, endDate), new Status(LocalDate.now(), startDate, endDate), 0, 0L, LocalDateTime.now(), null); } public static Session ofPaid(Long id, Long courseId, CoverImages coverImages, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee) { - return new Session(id, courseId, SessionType.PAID, coverImages, new Period(startDate, endDate), Status.NOT_OPEN, maxStudents, fee, LocalDateTime.now(), null); + return new Session(id, courseId, SessionType.PAID, coverImages, new Period(startDate, endDate), new Status(LocalDate.now(), startDate, endDate), maxStudents, fee, LocalDateTime.now(), null); } - public static Session of(Long id, Long courseId, SessionType type, CoverImages coverImages, Status status, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { - return new Session(id, courseId, type, coverImages, new Period(startDate, endDate), status, maxStudents, fee, createdAt, updatedAt); + public static Session of(Long id, Long courseId, SessionType type, CoverImages coverImages, RecruitmentStatus recruitmentStatus, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { + return new Session(id, courseId, type, coverImages, new Period(startDate, endDate), new Status(LocalDate.now(), startDate, endDate, recruitmentStatus), maxStudents, fee, createdAt, updatedAt); } private Session(Long id, Long courseId, SessionType type, CoverImages coverImages, Period period, Status status, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { @@ -59,20 +57,11 @@ public void register(Payment payment) { } protected void validateStatus() { - if (!status.isOpen()) { - throw new NotOpenSessionException(); - } - } - - public void openSession() { - if (!period.isDateWithinRange(LocalDate.now())) { - throw new OutOfSessionException(); - } - changeStatusOpen(); + this.status.validate(); } - private void changeStatusOpen() { - this.status = status.ofOpen(); + public void startRecruiting() { + this.status.startRecruiting(); } public Long id() { @@ -87,8 +76,12 @@ public String type() { return type.name(); } - public String status() { - return status.name(); + public String sessionStatus() { + return status.sessionStatus(); + } + + public String recruitmentStatus() { + return status.recruitmentStatus(); } public LocalDate startDate() { @@ -111,6 +104,7 @@ public Long fee() { public String toString() { return "Session{" + "id=" + id + + ", courseId=" + courseId + ", type=" + type + ", coverImages=" + coverImages + ", period=" + period + diff --git a/src/main/java/nextstep/courses/domain/SessionStatus.java b/src/main/java/nextstep/courses/domain/SessionStatus.java new file mode 100644 index 000000000..8138217b0 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionStatus.java @@ -0,0 +1,25 @@ +package nextstep.courses.domain; + +import java.time.LocalDate; + +public enum SessionStatus { + PREPARING, + IN_PROGRESS, + COMPLETED; + + public static SessionStatus of(LocalDate now, LocalDate startDate, LocalDate endDate) { + if (now.isBefore(startDate)) { + return PREPARING; + } + + if (now.isAfter(startDate) && now.isBefore(endDate)) { + return IN_PROGRESS; + } + + return COMPLETED; + } + + public boolean isInProgress() { + return this == IN_PROGRESS; + } +} diff --git a/src/main/java/nextstep/courses/domain/Status.java b/src/main/java/nextstep/courses/domain/Status.java index fec47173b..578e80a91 100644 --- a/src/main/java/nextstep/courses/domain/Status.java +++ b/src/main/java/nextstep/courses/domain/Status.java @@ -1,27 +1,42 @@ package nextstep.courses.domain; -import nextstep.courses.exception.InvalidSessionStatusException; +import nextstep.courses.exception.NotOpenSessionException; -import java.util.Arrays; +import java.time.LocalDate; -public enum Status { - NOT_OPEN, - OPEN, - CLOSED; +public class Status { + private SessionStatus sessionStatus; + private RecruitmentStatus recruitmentStatus; - public Status ofOpen() { - return OPEN; + public Status(LocalDate now, LocalDate startDate, LocalDate endDate) { + this(SessionStatus.of(now, startDate, endDate), RecruitmentStatus.NOT_RECRUITMENT); } - public boolean isOpen() { - return this == OPEN; + public Status(LocalDate now, LocalDate startDate, LocalDate endDate, RecruitmentStatus recruitmentStatus) { + this(SessionStatus.of(now, startDate, endDate), recruitmentStatus); } - public static Status findByName(String name) { - return Arrays.stream(values()) - .filter(status -> status.name().equalsIgnoreCase(name)) - .findFirst() - .orElseThrow(() -> new InvalidSessionStatusException(name)); + public Status(SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus) { + this.sessionStatus = sessionStatus; + this.recruitmentStatus = recruitmentStatus; + } + + public void validate() { + if (!sessionStatus.isInProgress() && !recruitmentStatus.isRecruiting()) { + throw new NotOpenSessionException(); + } + } + + public void startRecruiting() { + this.recruitmentStatus = recruitmentStatus.ofRecruiting(); + } + + public String sessionStatus() { + return this.sessionStatus.name(); + } + + public String recruitmentStatus() { + return this.recruitmentStatus.name(); } } diff --git a/src/main/java/nextstep/courses/exception/InvalidRecruitmentStatusException.java b/src/main/java/nextstep/courses/exception/InvalidRecruitmentStatusException.java new file mode 100644 index 000000000..10067cd5a --- /dev/null +++ b/src/main/java/nextstep/courses/exception/InvalidRecruitmentStatusException.java @@ -0,0 +1,8 @@ +package nextstep.courses.exception; + +public class InvalidRecruitmentStatusException extends RuntimeException { + + public InvalidRecruitmentStatusException(String code) { + super("존재하지 않는 강의 모집 상태입니다." + code); + } +} diff --git a/src/main/java/nextstep/courses/exception/InvalidSessionStatusException.java b/src/main/java/nextstep/courses/exception/InvalidSessionStatusException.java deleted file mode 100644 index 2411415f8..000000000 --- a/src/main/java/nextstep/courses/exception/InvalidSessionStatusException.java +++ /dev/null @@ -1,8 +0,0 @@ -package nextstep.courses.exception; - -public class InvalidSessionStatusException extends RuntimeException { - - public InvalidSessionStatusException(String code) { - super("존재하지 않는 강의 상태입니다." + code); - } -} diff --git a/src/main/java/nextstep/courses/exception/NotOpenSessionException.java b/src/main/java/nextstep/courses/exception/NotOpenSessionException.java index acad19812..68ada8d5a 100644 --- a/src/main/java/nextstep/courses/exception/NotOpenSessionException.java +++ b/src/main/java/nextstep/courses/exception/NotOpenSessionException.java @@ -2,6 +2,6 @@ public class NotOpenSessionException extends RuntimeException { public NotOpenSessionException() { - super("모집 중인 강의가 아닙니다."); + super("진행 중이거나 모집 중인 강의가 아닙니다."); } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index d21151901..64756538c 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,9 +1,9 @@ package nextstep.courses.infrastructure; import nextstep.courses.domain.CoverImages; +import nextstep.courses.domain.RecruitmentStatus; import nextstep.courses.domain.Session; import nextstep.courses.domain.SessionType; -import nextstep.courses.domain.Status; import nextstep.courses.repository.CoverImageRepository; import nextstep.courses.repository.SessionRepository; import org.springframework.jdbc.core.JdbcOperations; @@ -29,19 +29,19 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public int save(Session session) { - String sql = "insert into session2 (id, course_id, type, status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, session.id(), session.courseId(), session.type(), session.status(), session.startDate(), session.endDate(), session.maxStudents(), session.fee(), session.getCreatedAt()); + String sql = "insert into session2 (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, session.id(), session.courseId(), session.type(), session.recruitmentStatus(), session.startDate(), session.endDate(), session.maxStudents(), session.fee(), session.getCreatedAt()); } @Override public Session findById(Long id) { - String sql = "select id, course_id, type, status, start_date, end_date, max_students, fee, created_at, updated_at from session where id = ?"; + String sql = "select id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at, updated_at from session2 where id = ?"; RowMapper rowMapper = (rs, rowNum) -> Session.of( rs.getLong(1), rs.getLong(2), SessionType.findByCode(rs.getString(3)), coverImages(id), - Status.findByName(rs.getString(4)), + RecruitmentStatus.findByName(rs.getString(4)), toLocalDate(rs.getDate(5)), toLocalDate(rs.getDate(6)), rs.getInt(7), diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index a7feda8df..6c8488689 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -18,3 +18,5 @@ INSERT INTO cover_image (id, session_id, size, extension, width, height, created INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 2, 'FREE', 'NOT_OPEN', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 2, 'PAID', 'NOT_OPEN', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); +INSERT INTO session2 (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 'FREE', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); +INSERT INTO session2 (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 'PAID', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index d8a72fa90..870453051 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -80,7 +80,7 @@ create table session2 ( id bigint not null, course_id bigint not null, type varchar(10) not null, - status varchar(10) not null, + recruitment_status varchar(30) not null, start_date timestamp not null, end_date timestamp not null, max_students int, diff --git a/src/test/java/nextstep/courses/domain/SessionStatusTest.java b/src/test/java/nextstep/courses/domain/SessionStatusTest.java new file mode 100644 index 000000000..f7909fb68 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionStatusTest.java @@ -0,0 +1,24 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; + +class SessionStatusTest { + + @Test + @DisplayName("오늘 날짜에 맞는 준비중, 진행중, 종료를 반환한다.") + void create() { + SessionStatus preparing = SessionStatus.of(LocalDate.now(), LocalDate.of(2023, 12, 15), LocalDate.of(2023, 12, 31)); + assertThat(preparing).isEqualTo(SessionStatus.PREPARING); + + SessionStatus inProgress = SessionStatus.of(LocalDate.now(), LocalDate.of(2023, 12, 1), LocalDate.of(2023, 12, 31)); + assertThat(inProgress).isEqualTo(SessionStatus.IN_PROGRESS); + + SessionStatus completed = SessionStatus.of(LocalDate.now(), LocalDate.of(2023, 12, 1), LocalDate.of(2023, 12, 5)); + assertThat(completed).isEqualTo(SessionStatus.COMPLETED); + } +} diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index 8194194f0..75a7637d3 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -9,34 +9,34 @@ import java.time.LocalDate; import java.util.List; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; class SessionTest { private static final LocalDate START_DATE = LocalDate.of(2023, 12, 1); private static final LocalDate END_DATE = LocalDate.of(2023, 12, 31); - @Test - @DisplayName("무료 강의 수강 신청 시 강의 상태가 모집중이 아니면 예외를 던진다.") - void register_status_check() { - Session freeSession = Session.ofFree(1L, 2L, coverImages(), START_DATE, END_DATE); - assertThatThrownBy(() -> freeSession.register(Payment.ofFree(1L, NsUserTest.JAVAJIGI))) - .isInstanceOf(NotOpenSessionException.class); - } - - @Test - @DisplayName("강의 상태를 모집중으로 변경 시 현재 날짜가 강의 기간에 속하지 않으면 예외를 던진다.") - void register_open() { - Session session = Session.ofFree(1L, 2L, coverImages(), START_DATE, LocalDate.of(2023, 12, 2)); - assertThatThrownBy(() -> session.openSession()) - .isInstanceOf(OutOfSessionException.class); - } +// @Test +// @DisplayName("무료 강의 수강 신청 시 강의 상태가 모집중이 아니면 예외를 던진다.") +// void register_status_check() { +// Session freeSession = Session.ofFree(1L, 2L, coverImages(), START_DATE, END_DATE); +// assertThatThrownBy(() -> freeSession.register(Payment.ofFree(1L, NsUserTest.JAVAJIGI))) +// .isInstanceOf(NotOpenSessionException.class); +// } + +// @Test +// @DisplayName("강의 상태를 모집중으로 변경 시 현재 날짜가 강의 기간에 속하지 않으면 예외를 던진다.") +// void register_open() { +// Session session = Session.ofFree(1L, 2L, coverImages(), START_DATE, LocalDate.of(2023, 12, 2)); +// assertThatThrownBy(() -> session.startRecruiting()) +// .isInstanceOf(OutOfSessionException.class); +// } @Test @DisplayName("유료 강의 수강 신청 시 최대 수강 인원을 초과하면 예외를 던진다.") void register_over_students() { Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); - paidSession.openSession(); + paidSession.startRecruiting(); paidSession.register(Payment.ofPaid(1L, 1L, NsUserTest.JAVAJIGI, 10_000L)); @@ -48,7 +48,7 @@ void register_over_students() { @DisplayName("유료 강의 수강 신청 시 결제금액과 수강료가 일치하는지 확인하지 않으면 예외를 던진다.") void session_fee_test() { Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); - paidSession.openSession(); + paidSession.startRecruiting(); assertThatThrownBy(() -> paidSession.register(Payment.ofPaid(2L, 1L, NsUserTest.SANJIGI, 8_000L))) .isInstanceOf(PaymentMismatchException.class); @@ -65,7 +65,7 @@ void session_paid_condition_null() { @DisplayName("중복 수강 신청 시 예외를 던진다.") void duplicate_register() { Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 2, 10_000L); - paidSession.openSession(); + paidSession.startRecruiting(); paidSession.register(Payment.ofPaid(1L, 1L, NsUserTest.SANJIGI, 10_000L)); @@ -80,6 +80,22 @@ void empty_images() { .isInstanceOf(EmptyCoverImageException.class); } + @Test + @DisplayName("강의 생성 시 기간 내면 상태가 준비중이고 모집 전이다.") + void session_status() { + Session session = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); + assertThat(session.sessionStatus()).isEqualTo(SessionStatus.IN_PROGRESS.name()); + assertThat(session.recruitmentStatus()).isEqualTo(RecruitmentStatus.NOT_RECRUITMENT.name()); + } + + @Test + @DisplayName("강의 상태가 모집중이 아니어도 진행 중이면 수강신청이 가능하다.") + void can_register_not_recruitment() { + Session freeSession = Session.ofFree(1L, 2L, coverImages(), START_DATE, END_DATE); + assertThatCode(() -> freeSession.register(Payment.ofFree(1L, NsUserTest.JAVAJIGI))) + .doesNotThrowAnyException(); + } + private static CoverImages coverImages() { return new CoverImages(List.of(new CoverImage(1, "gif", 300, 200))); } diff --git a/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java index f3f5b6ed6..db57a6084 100644 --- a/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java @@ -37,7 +37,7 @@ void setUp() { void crud() { NsUser nsUser = new NsUser(1L, "javajigi", "test", "자바지기", "javajigi@slipp.net"); Session session = sessionRepository.findById(3L); - session.openSession(); + session.startRecruiting(); session.register(Payment.ofPaid(1L, 3L, nsUser, 20_000L)); Long id = studentsRepository.save(nsUser.getId(), 3L); From e7af10546ca13c98673583fc9d4b856f81bb66a5 Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Thu, 14 Dec 2023 02:02:44 +0900 Subject: [PATCH 7/8] =?UTF-8?q?remove:=20=EA=B0=95=EC=9D=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EA=B0=80=20=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=BC?= =?UTF-8?q?=EB=95=8C=EB=8F=84=20=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95(as-is?= =?UTF-8?q?=20->=20to-be)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/JdbcSessionRepository.java | 4 ++-- src/main/resources/data.sql | 7 ++----- src/main/resources/schema.sql | 15 --------------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 64756538c..cd6bb4add 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -29,13 +29,13 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public int save(Session session) { - String sql = "insert into session2 (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String sql = "insert into session (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; return jdbcTemplate.update(sql, session.id(), session.courseId(), session.type(), session.recruitmentStatus(), session.startDate(), session.endDate(), session.maxStudents(), session.fee(), session.getCreatedAt()); } @Override public Session findById(Long id) { - String sql = "select id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at, updated_at from session2 where id = ?"; + String sql = "select id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at, updated_at from session where id = ?"; RowMapper rowMapper = (rs, rowNum) -> Session.of( rs.getLong(1), rs.getLong(2), diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 6c8488689..118092d41 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -15,8 +15,5 @@ INSERT INTO cover_image (id, session_id, size, extension, width, height, created INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (3, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (4, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); -INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 2, 'FREE', 'NOT_OPEN', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); -INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 2, 'PAID', 'NOT_OPEN', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); - -INSERT INTO session2 (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 'FREE', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); -INSERT INTO session2 (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 'PAID', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); +INSERT INTO session (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 'FREE', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); +INSERT INTO session (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 'PAID', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 870453051..0b9580c0d 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -62,21 +62,6 @@ create table cover_image ( ); create table session ( - id bigint not null, - course_id bigint not null, - image_id bigint not null, - type varchar(10) not null, - status varchar(10) not null, - start_date timestamp not null, - end_date timestamp not null, - max_students int, - fee int, - created_at timestamp not null, - updated_at timestamp, - primary key (id) -); - -create table session2 ( id bigint not null, course_id bigint not null, type varchar(10) not null, From 0c96d791ca9780b830ed20062a29e692f9dab6ad Mon Sep 17 00:00:00 2001 From: kyunghyun-park Date: Thu, 14 Dec 2023 02:02:54 +0900 Subject: [PATCH 8/8] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- md/step4.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/md/step4.md b/md/step4.md index 5613eb2bb..5e863ca82 100644 --- a/md/step4.md +++ b/md/step4.md @@ -10,11 +10,11 @@ ## 변경된 기능 요구사항 ### **강의 수강신청은 강의 상태가 모집중일 때만 가능하다.** -- [ ] 강의가 진행 중인 상태에서도 수강신청이 가능해야 한다. +- [X] 강의가 진행 중인 상태에서도 수강신청이 가능해야 한다. - 강의 진행 상태(준비중, 진행중, 종료)와 모집 상태(비모집중, 모집중)로 상태 값을 분리해야 한다. ### **강의는 강의 커버 이미지 정보를 가진다.** -- [ ] 강의는 **하나 이상**의 커버 이미지를 가질 수 있다. +- [X] 강의는 **하나 이상**의 커버 이미지를 가질 수 있다. ### 강사가 승인하지 않아도 수강 신청하는 모든 사람이 수강 가능하다. - [ ] 우아한테크코스(무료), 우아한테크캠프 Pro(유료)와 같이 선발된 인원만 수강 가능해야 한다.