From 1b625b07bc998ca9d094471f81d867947b2a637d Mon Sep 17 00:00:00 2001 From: h3yon Date: Sat, 9 Dec 2023 20:07:05 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feature:=20=F0=9F=9A=80=203=EB=8B=A8?= =?UTF-8?q?=EA=B3=84=20-=20=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD(DB=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/common/domain/BaseEntity.java | 23 +++++ .../nextstep/common/utils/DateTimeUtils.java | 20 +++++ .../java/nextstep/courses/domain/Course.java | 18 +--- .../courses/domain/CourseRepository.java | 6 +- .../nextstep/courses/domain/Enrollment.java | 35 ++++++-- .../courses/domain/EnrollmentRepository.java | 9 ++ .../courses/domain/image/CoverImage.java | 31 ++++++- .../courses/domain/image/ImageRepository.java | 9 ++ .../domain/session/EnrollmentCount.java | 15 +++- .../courses/domain/session/FreeSession.java | 16 ---- .../courses/domain/session/PaidSession.java | 33 ------- .../courses/domain/session/Session.java | 90 ++++++++++++++++--- .../courses/domain/session/SessionFee.java | 29 +++++- .../domain/session/SessionFeeType.java | 12 +++ .../courses/domain/session/SessionPeriod.java | 24 +++-- .../domain/session/SessionRepository.java | 9 ++ .../infrastructure/JdbcCourseRepository.java | 33 ++++--- .../JdbcEnrollmentRepository.java | 52 +++++++++++ .../image/JdbcImageRepository.java | 51 +++++++++++ .../session/JdbcSessionRepository.java | 88 ++++++++++++++++++ .../courses/service/EnrollmentService.java | 28 +++++- .../nextstep/payments/domain/Payment.java | 22 +++-- .../nextstep/users/domain/UserRepository.java | 2 + .../infrastructure/JdbcUserRepository.java | 21 +++-- src/main/resources/schema.sql | 40 +++++++++ .../courses/domain/image/CoverImageTest.java | 2 +- .../courses/domain/session/SessionTest.java | 14 +-- .../infrastructure/CourseRepositoryTest.java | 5 +- .../JdbcEnrollmentRepositoryTest.java | 73 +++++++++++++++ .../image/JdbcImageRepositoryTest.java | 41 +++++++++ .../session/JdbcSessionRepositoryTest.java | 55 ++++++++++++ .../nextstep/courses/mock/MockCourse.java | 22 +++++ 32 files changed, 792 insertions(+), 136 deletions(-) create mode 100644 src/main/java/nextstep/common/domain/BaseEntity.java create mode 100644 src/main/java/nextstep/common/utils/DateTimeUtils.java create mode 100644 src/main/java/nextstep/courses/domain/EnrollmentRepository.java create mode 100644 src/main/java/nextstep/courses/domain/image/ImageRepository.java delete mode 100644 src/main/java/nextstep/courses/domain/session/FreeSession.java delete mode 100644 src/main/java/nextstep/courses/domain/session/PaidSession.java create mode 100644 src/main/java/nextstep/courses/domain/session/SessionFeeType.java create mode 100644 src/main/java/nextstep/courses/domain/session/SessionRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/image/JdbcImageRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java create mode 100644 src/test/java/nextstep/courses/infrastructure/JdbcEnrollmentRepositoryTest.java create mode 100644 src/test/java/nextstep/courses/infrastructure/image/JdbcImageRepositoryTest.java create mode 100644 src/test/java/nextstep/courses/infrastructure/session/JdbcSessionRepositoryTest.java create mode 100644 src/test/java/nextstep/courses/mock/MockCourse.java diff --git a/src/main/java/nextstep/common/domain/BaseEntity.java b/src/main/java/nextstep/common/domain/BaseEntity.java new file mode 100644 index 000000000..1909fdb65 --- /dev/null +++ b/src/main/java/nextstep/common/domain/BaseEntity.java @@ -0,0 +1,23 @@ +package nextstep.common.domain; + +import java.time.LocalDateTime; + +public class BaseEntity { + protected Long id; + protected LocalDateTime createdAt; + protected LocalDateTime updatedAt; + + public BaseEntity(final Long id, final LocalDateTime createdAt, final LocalDateTime updatedAt) { + this.id = id; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return this.id; + } + + public LocalDateTime getCreatedAt() { + return this.createdAt; + } +} diff --git a/src/main/java/nextstep/common/utils/DateTimeUtils.java b/src/main/java/nextstep/common/utils/DateTimeUtils.java new file mode 100644 index 000000000..12de5b962 --- /dev/null +++ b/src/main/java/nextstep/common/utils/DateTimeUtils.java @@ -0,0 +1,20 @@ +package nextstep.common.utils; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +public class DateTimeUtils { + public static Timestamp toTimeStamp(LocalDateTime localDateTime) { + if (localDateTime == null) { + return null; + } + return Timestamp.valueOf(localDateTime); + } + + public static LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } +} diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/Course.java index 6defe339a..a175f6116 100644 --- a/src/main/java/nextstep/courses/domain/Course.java +++ b/src/main/java/nextstep/courses/domain/Course.java @@ -1,25 +1,19 @@ package nextstep.courses.domain; +import nextstep.common.domain.BaseEntity; import nextstep.courses.domain.session.Session; import java.time.LocalDateTime; import java.util.List; -public class Course { +public class Course extends BaseEntity { private Long id; private String title; private Long creatorId; - - private LocalDateTime createdAt; - - private LocalDateTime updatedAt; private List sessions; - public Course() { - } - public Course(String title, Long creatorId) { this(0L, title, creatorId, LocalDateTime.now(), null, null); } @@ -28,11 +22,9 @@ public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, Lo this(0L, title, creatorId, createdAt, updatedAt, null); } public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt, List sessions) { - this.id = id; + super(id, createdAt, updatedAt); this.title = title; this.creatorId = creatorId; - this.createdAt = createdAt; - this.updatedAt = updatedAt; this.sessions = sessions; } @@ -44,10 +36,6 @@ public Long getCreatorId() { return creatorId; } - public LocalDateTime getCreatedAt() { - return createdAt; - } - @Override public String toString() { return "Course{" + diff --git a/src/main/java/nextstep/courses/domain/CourseRepository.java b/src/main/java/nextstep/courses/domain/CourseRepository.java index 6aaeb638d..26b5b1998 100644 --- a/src/main/java/nextstep/courses/domain/CourseRepository.java +++ b/src/main/java/nextstep/courses/domain/CourseRepository.java @@ -1,7 +1,9 @@ package nextstep.courses.domain; +import java.util.Optional; + public interface CourseRepository { - int save(Course course); + long save(Course course); - Course findById(Long id); + Optional findById(Long id); } diff --git a/src/main/java/nextstep/courses/domain/Enrollment.java b/src/main/java/nextstep/courses/domain/Enrollment.java index 76bab54f6..7d7f454c3 100644 --- a/src/main/java/nextstep/courses/domain/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/Enrollment.java @@ -1,12 +1,29 @@ package nextstep.courses.domain; -public class Enrollment{ - private Long id; - private Long nsUserId; - private Long sessionId; - - public Enrollment(final Long nsUserId, final Long sessionId) { - this.nsUserId = nsUserId; - this.sessionId = sessionId; - } +import nextstep.common.domain.BaseEntity; + +import java.time.LocalDateTime; + +public class Enrollment extends BaseEntity { + private Long nsUserId; + private Long sessionId; + + + public Enrollment(final Long nsUserId, final Long sessionId) { + this(null, nsUserId, sessionId, LocalDateTime.now(), null); + } + + public Enrollment(final Long id, final Long nsUserId, final Long sessionId, LocalDateTime createAt, LocalDateTime updatedAt) { + super(id, createAt, updatedAt); + this.nsUserId = nsUserId; + this.sessionId = sessionId; + } + + public Long nsUserId() { + return nsUserId; + } + + public Long sessionId() { + return sessionId; + } } diff --git a/src/main/java/nextstep/courses/domain/EnrollmentRepository.java b/src/main/java/nextstep/courses/domain/EnrollmentRepository.java new file mode 100644 index 000000000..67626ec2c --- /dev/null +++ b/src/main/java/nextstep/courses/domain/EnrollmentRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain; + +import java.util.Optional; + +public interface EnrollmentRepository { + long save(Enrollment enrollment); + + Optional findById(Long id); +} diff --git a/src/main/java/nextstep/courses/domain/image/CoverImage.java b/src/main/java/nextstep/courses/domain/image/CoverImage.java index c13bce9f3..d29ad91c8 100644 --- a/src/main/java/nextstep/courses/domain/image/CoverImage.java +++ b/src/main/java/nextstep/courses/domain/image/CoverImage.java @@ -8,17 +8,24 @@ public class CoverImage { private static final int MIN_WIDTH = 300; private static final int MIN_HEIGHT = 200; private static final Long MAX_SIZE = 1024L * 1024L; + private final Long id; private final int width; private final int height; private final long size; private final ImageType imageType; - public CoverImage(final long size, final int width, final int height, final ImageType imageType) { + + public CoverImage(int width, int height, long size, ImageType imageType) { + this(null, width, height, size, imageType); + } + + public CoverImage(Long id, int width, int height, long size, ImageType imageType) { validateImage(size, width, height); - this.size = size; + this.id = id; this.width = width; this.height = height; + this.size = size; this.imageType = imageType; } @@ -45,4 +52,24 @@ private void validatePixel(final int width, final int height) { throw new IllegalArgumentException(INVALID_IMAGE_PIXEL_MESSAGE); } } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public long getSize() { + return size; + } + + public ImageType getImageType() { + return imageType; + } + + public Long getId() { + return id; + } } diff --git a/src/main/java/nextstep/courses/domain/image/ImageRepository.java b/src/main/java/nextstep/courses/domain/image/ImageRepository.java new file mode 100644 index 000000000..32537f651 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/image/ImageRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain.image; + +import java.util.Optional; + +public interface ImageRepository { + long save(CoverImage coverImage); + + Optional findById(Long id); +} diff --git a/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java b/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java index 148348598..845acefc2 100644 --- a/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java +++ b/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java @@ -1,7 +1,7 @@ package nextstep.courses.domain.session; public class EnrollmentCount { - private int availableCount; + private final int availableCount; private int remainCount; public EnrollmentCount(final int availableCount) { @@ -9,6 +9,11 @@ public EnrollmentCount(final int availableCount) { this.remainCount = availableCount; } + public EnrollmentCount(final int maxEnrollmentCount, final int remainEnrollmentCount) { + this.availableCount = maxEnrollmentCount; + this.remainCount = remainEnrollmentCount; + } + public boolean isNoRemaining() { return remainCount <= 0; } @@ -16,4 +21,12 @@ public boolean isNoRemaining() { public void decrease() { this.remainCount--; } + + public int getAvailableCount() { + return availableCount; + } + + public int getRemainCount() { + return remainCount; + } } diff --git a/src/main/java/nextstep/courses/domain/session/FreeSession.java b/src/main/java/nextstep/courses/domain/session/FreeSession.java deleted file mode 100644 index 7611a071a..000000000 --- a/src/main/java/nextstep/courses/domain/session/FreeSession.java +++ /dev/null @@ -1,16 +0,0 @@ -package nextstep.courses.domain.session; - -import nextstep.courses.domain.image.CoverImage; -import nextstep.payments.domain.Payment; - -public class FreeSession extends Session { - - public FreeSession(final Long id, final String title, final SessionPeriod sessionPeriod, final nextstep.courses.domain.session.SessionState sessionState, final CoverImage coverImage) { - super(id, title, sessionPeriod, sessionState, coverImage); - } - - @Override - public void enroll(Payment payment) { - validateSessionState(); - } -} diff --git a/src/main/java/nextstep/courses/domain/session/PaidSession.java b/src/main/java/nextstep/courses/domain/session/PaidSession.java deleted file mode 100644 index 0a5a3178b..000000000 --- a/src/main/java/nextstep/courses/domain/session/PaidSession.java +++ /dev/null @@ -1,33 +0,0 @@ -package nextstep.courses.domain.session; - -import nextstep.courses.domain.image.CoverImage; -import nextstep.payments.domain.Payment; - -public class PaidSession extends Session { - public static final String INVALID_FEE_MESSAGE = "결제한 금액과 강의의 금액이 다릅니다."; - - private final SessionFee sessionFee; - private final EnrollmentCount enrollmentCount; - - public PaidSession(final Long id, final String title, final SessionPeriod sessionPeriod, final nextstep.courses.domain.session.SessionState sessionState, final CoverImage coverImage, final SessionFee sessionFee, final int enrollmentCount) { - super(id, title, sessionPeriod, sessionState, coverImage); - this.sessionFee = sessionFee; - this.enrollmentCount = new EnrollmentCount(enrollmentCount); - } - - @Override - public void enroll(final Payment payment) { - checkEnrollmentAvailable(payment); - validateSessionState(); - enrollmentCount.decrease(); - } - - private void checkEnrollmentAvailable(final Payment payment) { - if (enrollmentCount.isNoRemaining()) { - throw new IllegalArgumentException(NO_AVAILABLE_SEATS_MESSAGE); - } - if (sessionFee.isNotEqualTo(payment)) { - throw new IllegalArgumentException(INVALID_FEE_MESSAGE); - } - } -} diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index bd39fef9b..6e79070ce 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -1,35 +1,105 @@ package nextstep.courses.domain.session; +import nextstep.common.domain.BaseEntity; import nextstep.courses.domain.image.CoverImage; import nextstep.payments.domain.Payment; -public abstract class Session { - public static final String NO_AVAILABLE_STATE_MESSAGE = "강의 수강신청은 강의 상태가 모집중일 때만 가능합니다."; - public static final String NO_AVAILABLE_SEATS_MESSAGE = "강의 수강 인원이 찼습니다"; +import java.time.LocalDateTime; - private final Long id; - private final String name; +public class Session extends BaseEntity { + private static final String NO_AVAILABLE_STATE_MESSAGE = "강의 수강신청은 강의 상태가 모집중일 때만 가능합니다."; + private static final String NO_AVAILABLE_SEATS_MESSAGE = "강의 수강 인원이 찼습니다"; + private static final String INVALID_FEE_MESSAGE = "결제한 금액과 강의의 금액이 다릅니다."; + + private final String title; private final SessionPeriod sessionPeriod; private final SessionState SessionState; private final CoverImage coverImage; - public Session(Long id, String name, SessionPeriod sessionPeriod, nextstep.courses.domain.session.SessionState sessionState, CoverImage coverImage) { + private SessionFeeType sessionFeeType; + private SessionFee sessionFee; + private EnrollmentCount enrollmentCount; + + public Session(final String title, + final SessionPeriod sessionPeriod, + final SessionState sessionState, + final CoverImage coverImage, + final SessionFee sessionFee, + final EnrollmentCount enrollmentCount) { + this(null, title, sessionPeriod, sessionState, coverImage, SessionFeeType.toSessionFeeType(sessionFee), sessionFee, enrollmentCount, + LocalDateTime.now(), null); + } + + public Session(final Long id, + final String title, + final SessionPeriod sessionPeriod, + final SessionState sessionState, + final CoverImage coverImage, + final SessionFeeType sessionFeeType, + final SessionFee sessionFee, + final EnrollmentCount enrollmentCount, + final LocalDateTime createAt, + LocalDateTime updatedAt) { + super(id, createAt, updatedAt); this.id = id; - this.name = name; + this.title = title; this.sessionPeriod = sessionPeriod; this.SessionState = sessionState; this.coverImage = coverImage; + this.sessionFeeType = sessionFeeType; + this.sessionFee = sessionFee; + this.enrollmentCount = enrollmentCount; } - protected void validateSessionState() { + public void validateSessionState() { if (SessionState != nextstep.courses.domain.session.SessionState.RECRUITING) { throw new IllegalArgumentException(NO_AVAILABLE_STATE_MESSAGE); } } - public Long id() { + public Long getId() { return this.id; } - public abstract void enroll(final Payment payment); + public String getTitle() { + return title; + } + + public SessionPeriod getSessionPeriod() { + return sessionPeriod; + } + + public SessionState getSessionState() { + return SessionState; + } + + public long getCoverImageId() { + return coverImage.getId(); + } + + public SessionFeeType getSessionFeeType() { + return sessionFeeType; + } + + public SessionFee getSessionFee() { + return sessionFee; + } + + public EnrollmentCount getEnrollmentCount() { + return enrollmentCount; + } + + public void validateEnrollState(Payment payment) { + validateSessionState(); + if (sessionFee.isFree()) { + return; + } + if (enrollmentCount.isNoRemaining()) { + throw new IllegalArgumentException(NO_AVAILABLE_SEATS_MESSAGE); + } + if (sessionFee.isNotEqualTo(payment)) { + throw new IllegalArgumentException(INVALID_FEE_MESSAGE); + } + enrollmentCount.decrease(); + } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionFee.java b/src/main/java/nextstep/courses/domain/session/SessionFee.java index 79c30a9fb..ed0dbf6ca 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionFee.java +++ b/src/main/java/nextstep/courses/domain/session/SessionFee.java @@ -2,6 +2,8 @@ import nextstep.payments.domain.Payment; +import java.util.Objects; + public class SessionFee { private static final String INVALID_FEE_MESSAGE = "강의 금액을 확인해주세요."; @@ -18,7 +20,32 @@ public boolean isFree() { return fee == 0; } + public static SessionFee ZERO() { + return new SessionFee(0L); + } + + public static SessionFee of(Long fee) { + return new SessionFee(fee); + } + public boolean isNotEqualTo(final Payment payment) { - return !fee.equals(payment.getAmount()); + return !fee.equals(payment.getSessionFee()); + } + + public Long getFee() { + return fee; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SessionFee that = (SessionFee) o; + return Objects.equals(fee, that.fee); + } + + @Override + public int hashCode() { + return Objects.hash(fee); } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionFeeType.java b/src/main/java/nextstep/courses/domain/session/SessionFeeType.java new file mode 100644 index 000000000..1de94d609 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionFeeType.java @@ -0,0 +1,12 @@ +package nextstep.courses.domain.session; + +public enum SessionFeeType { + PAID, FREE; + + public static SessionFeeType toSessionFeeType(SessionFee sessionFee) { + if (sessionFee.isFree()) { + return FREE; + } + return PAID; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/SessionPeriod.java b/src/main/java/nextstep/courses/domain/session/SessionPeriod.java index 27c74071d..b8e2d3a25 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionPeriod.java +++ b/src/main/java/nextstep/courses/domain/session/SessionPeriod.java @@ -6,13 +6,13 @@ public class SessionPeriod { public static final String INVALID_PERIOD_MESSAGE = "강의 기간을 확인해주세요."; - private final LocalDateTime startDateTime; - private final LocalDateTime endDateTime; + private final LocalDateTime startAt; + private final LocalDateTime endAt; - public SessionPeriod(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { - validateSessionPeriod(startDateTime, endDateTime); - this.startDateTime = startDateTime; - this.endDateTime = endDateTime; + public SessionPeriod(final LocalDateTime startAt, final LocalDateTime endAt) { + validateSessionPeriod(startAt, endAt); + this.startAt = startAt; + this.endAt = endAt; } private void validateSessionPeriod(LocalDateTime startDateTime, LocalDateTime endDateTime) { @@ -26,11 +26,19 @@ public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final SessionPeriod that = (SessionPeriod) o; - return Objects.equals(startDateTime, that.startDateTime) && Objects.equals(endDateTime, that.endDateTime); + return Objects.equals(startAt, that.startAt) && Objects.equals(endAt, that.endAt); } @Override public int hashCode() { - return Objects.hash(startDateTime, endDateTime); + return Objects.hash(startAt, endAt); + } + + public LocalDateTime getStartAt() { + return startAt; + } + + public LocalDateTime getEndAt() { + return endAt; } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/session/SessionRepository.java new file mode 100644 index 000000000..e6b17764c --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain.session; + +import java.util.Optional; + +public interface SessionRepository { + long save(Long courseId, Session session); + + Optional findById(Long id); +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index f9122cbe3..9e5ae0c0a 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -4,10 +4,15 @@ import nextstep.courses.domain.CourseRepository; 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.Timestamp; -import java.time.LocalDateTime; +import java.sql.PreparedStatement; +import java.util.Optional; + +import static nextstep.common.utils.DateTimeUtils.toLocalDateTime; +import static nextstep.common.utils.DateTimeUtils.toTimeStamp; @Repository("courseRepository") public class JdbcCourseRepository implements CourseRepository { @@ -18,13 +23,22 @@ public JdbcCourseRepository(JdbcOperations jdbcTemplate) { } @Override - public int save(Course course) { + public long save(Course course) { String sql = "insert into course (title, creator_id, created_at) values(?, ?, ?)"; - return jdbcTemplate.update(sql, course.getTitle(), course.getCreatorId(), course.getCreatedAt()); + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(con -> { + PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"}); + ps.setString(1, course.getTitle()); + ps.setLong(2, course.getCreatorId()); + ps.setTimestamp(3, toTimeStamp(course.getCreatedAt())); + return ps; + }, keyHolder); + Number key = keyHolder.getKey(); + return key != null ? key.longValue() : -1; } @Override - public Course findById(Long id) { + public Optional findById(Long id) { String sql = "select id, title, creator_id, created_at, updated_at from course where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Course( rs.getLong(1), @@ -32,13 +46,6 @@ public Course findById(Long id) { rs.getLong(3), toLocalDateTime(rs.getTimestamp(4)), toLocalDateTime(rs.getTimestamp(5))); - return jdbcTemplate.queryForObject(sql, rowMapper, id); - } - - private LocalDateTime toLocalDateTime(Timestamp timestamp) { - if (timestamp == null) { - return null; - } - return timestamp.toLocalDateTime(); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java new file mode 100644 index 000000000..cd0fb1873 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java @@ -0,0 +1,52 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.Enrollment; +import nextstep.courses.domain.EnrollmentRepository; +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.util.Optional; + +import static nextstep.common.utils.DateTimeUtils.toLocalDateTime; +import static nextstep.common.utils.DateTimeUtils.toTimeStamp; + +@Repository("enrollmentRepository") +public class JdbcEnrollmentRepository implements EnrollmentRepository { + private JdbcOperations jdbcTemplate; + + public JdbcEnrollmentRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public long save(final Enrollment enrollment) { + String sql = "insert into enrollment (user_id, session_id, created_at) values(?, ?, ?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(con -> { + PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, enrollment.nsUserId()); + ps.setLong(2, enrollment.sessionId()); + ps.setTimestamp(3, toTimeStamp(enrollment.getCreatedAt())); + return ps; + }, keyHolder); + + Number key = keyHolder.getKey(); + return key != null ? key.longValue() : -1; + } + + @Override + public Optional findById(final Long id) { + String sql = "select id, user_id, session_id, created_at, updated_at from enrollment where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Enrollment( + rs.getLong(1), + rs.getLong(2), + rs.getLong(3), + toLocalDateTime(rs.getTimestamp(4)), + toLocalDateTime(rs.getTimestamp(5))); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/image/JdbcImageRepository.java b/src/main/java/nextstep/courses/infrastructure/image/JdbcImageRepository.java new file mode 100644 index 000000000..ad70c28e8 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/image/JdbcImageRepository.java @@ -0,0 +1,51 @@ +package nextstep.courses.infrastructure.image; + +import nextstep.courses.domain.image.CoverImage; +import nextstep.courses.domain.image.ImageRepository; +import nextstep.courses.domain.image.ImageType; +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.util.Optional; + +@Repository("imageRepository") +public class JdbcImageRepository implements ImageRepository { + private JdbcOperations jdbcTemplate; + + public JdbcImageRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public long save(final CoverImage image) { + String sql = "insert into image (width, height, size, type) values(?, ?, ?, ?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(con -> { + PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, image.getWidth()); + ps.setLong(2, image.getHeight()); + ps.setLong(3, image.getSize()); + ps.setString(4, image.getImageType().name()); + return ps; + }, keyHolder); + + Number key = keyHolder.getKey(); + return key != null ? key.longValue() : -1; + } + + @Override + public Optional findById(final Long id) { + String sql = "select id, width, height, `size`, type from image where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new CoverImage( + rs.getLong(1), + rs.getInt(2), + rs.getInt(3), + rs.getLong(4), + ImageType.valueOf(rs.getString(5))); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java new file mode 100644 index 000000000..c6f1a6e1a --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java @@ -0,0 +1,88 @@ +package nextstep.courses.infrastructure.session; + +import nextstep.courses.domain.image.CoverImage; +import nextstep.courses.domain.image.ImageRepository; +import nextstep.courses.domain.session.EnrollmentCount; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionFee; +import nextstep.courses.domain.session.SessionFeeType; +import nextstep.courses.domain.session.SessionPeriod; +import nextstep.courses.domain.session.SessionRepository; +import nextstep.courses.domain.session.SessionState; +import nextstep.courses.infrastructure.image.JdbcImageRepository; +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.time.LocalDateTime; +import java.util.Optional; + +import static nextstep.common.utils.DateTimeUtils.toLocalDateTime; +import static nextstep.common.utils.DateTimeUtils.toTimeStamp; + +@Repository("sessionRepository") +public class JdbcSessionRepository implements SessionRepository { + private final ImageRepository imageRepository; + private JdbcOperations jdbcTemplate; + + public JdbcSessionRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.imageRepository = new JdbcImageRepository(jdbcTemplate); + } + + @Override + public long save(Long courseId, Session session) { + String sql = "insert into session (title, start_at, end_at, state, course_id, image_id, fee_type, " + + " session_fee, available_count, remain_count, created_at) " + + " values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + SessionPeriod sessionPeriod = session.getSessionPeriod(); + EnrollmentCount enrollmentCount = + (session.getSessionFeeType() == SessionFeeType.PAID) ? session.getEnrollmentCount() + : new EnrollmentCount(0, 0); + + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(con -> { + PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"}); + ps.setString(1, session.getTitle()); + ps.setTimestamp(2, toTimeStamp(sessionPeriod.getStartAt())); + ps.setTimestamp(3, toTimeStamp(sessionPeriod.getEndAt())); + ps.setString(4, session.getSessionState().name()); + ps.setLong(5, courseId); + ps.setLong(6, session.getCoverImageId()); + ps.setString(7, session.getSessionFeeType().name()); + ps.setLong(8, session.getSessionFee().getFee()); + ps.setInt(9, enrollmentCount.getAvailableCount()); + ps.setInt(10, enrollmentCount.getRemainCount()); + ps.setTimestamp(11, toTimeStamp(LocalDateTime.now())); + return ps; + }, keyHolder); + + Number key = keyHolder.getKey(); + return key != null ? key.longValue() : -1; + } + + @Override + public Optional findById(final Long id) { + String sql = "select id, title, start_at, end_at, state, course_id, image_id, fee_type," + + " session_fee, available_count, remain_count, created_at, updated_at from session where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Session( + rs.getLong(1), + rs.getString(2), + new SessionPeriod(toLocalDateTime(rs.getTimestamp(3)), toLocalDateTime(rs.getTimestamp(4))), + SessionState.valueOf(rs.getString(5)), + findByCoverImage(rs.getLong(7)), + SessionFeeType.valueOf(rs.getString(8)), + SessionFee.of(rs.getLong(9)), + new EnrollmentCount(rs.getInt(10), rs.getInt(11)), + toLocalDateTime(rs.getTimestamp(12)), + toLocalDateTime(rs.getTimestamp(13))); + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); + } + + private CoverImage findByCoverImage(final Long id) { + return imageRepository.findById(id).get(); + } +} diff --git a/src/main/java/nextstep/courses/service/EnrollmentService.java b/src/main/java/nextstep/courses/service/EnrollmentService.java index 446aac6ce..dc47054f2 100644 --- a/src/main/java/nextstep/courses/service/EnrollmentService.java +++ b/src/main/java/nextstep/courses/service/EnrollmentService.java @@ -1,13 +1,35 @@ package nextstep.courses.service; import nextstep.courses.domain.Enrollment; +import nextstep.courses.domain.EnrollmentRepository; import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionRepository; import nextstep.payments.domain.Payment; import nextstep.users.domain.NsUser; +import nextstep.users.domain.UserRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +@Service public class EnrollmentService { - public Enrollment enroll(NsUser nsUser, Session session, Payment payment) { - session.enroll(payment); - return new Enrollment(nsUser.getId(), session.id()); + private SessionRepository sessionRepository; + private EnrollmentRepository enrollmentRepository; + private UserRepository userRepository; + + @Transactional + public long enroll(Long courseId, Payment payment) { + Session session = sessionRepository.findById(payment.getSessionId()) + .orElseThrow(() -> new IllegalArgumentException("신청할 강의를 확인해주세요.")); + NsUser user = userRepository.findById(payment.getNsUserId()) + .orElseThrow(() -> new IllegalArgumentException("유저 정보를 확인해주세요.")); + Enrollment enroll = enroll(session, user, payment); + sessionRepository.save(courseId, session); + return enrollmentRepository.save(enroll); + } + + public Enrollment enroll(final Session session, final NsUser user, final Payment payment) { + session.validateEnrollState(payment); + return new Enrollment(user.getId(), payment.getSessionId()); } + } diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java index 7c82f2a4d..c065f71c5 100644 --- a/src/main/java/nextstep/payments/domain/Payment.java +++ b/src/main/java/nextstep/payments/domain/Payment.java @@ -12,22 +12,34 @@ public class Payment { private Long nsUserId; // 결제 금액 - private Long amount; + private Long sessionFee; private LocalDateTime createdAt; public Payment() { } - public Payment(String id, Long sessionId, Long nsUserId, Long amount) { + public Payment(String id, Long sessionId, Long nsUserId, Long sessionFee) { this.id = id; this.sessionId = sessionId; this.nsUserId = nsUserId; - this.amount = amount; + this.sessionFee = sessionFee; this.createdAt = LocalDateTime.now(); } - public Long getAmount() { - return this.amount; + public Long getSessionFee() { + return this.sessionFee; + } + + public String getId() { + return id; + } + + public Long getSessionId() { + return sessionId; + } + + public Long getNsUserId() { + return nsUserId; } } diff --git a/src/main/java/nextstep/users/domain/UserRepository.java b/src/main/java/nextstep/users/domain/UserRepository.java index 745a85dc1..d005a6986 100755 --- a/src/main/java/nextstep/users/domain/UserRepository.java +++ b/src/main/java/nextstep/users/domain/UserRepository.java @@ -4,4 +4,6 @@ public interface UserRepository { Optional findByUserId(String userId); + + Optional findById(Long id); } diff --git a/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java b/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java index 005f04fc5..e30eb4d61 100644 --- a/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java +++ b/src/main/java/nextstep/users/infrastructure/JdbcUserRepository.java @@ -6,10 +6,10 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; -import java.sql.Timestamp; -import java.time.LocalDateTime; import java.util.Optional; +import static nextstep.common.utils.DateTimeUtils.toLocalDateTime; + @Repository("userRepository") public class JdbcUserRepository implements UserRepository { private JdbcOperations jdbcTemplate; @@ -32,10 +32,17 @@ public Optional findByUserId(String userId) { return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, userId)); } - private LocalDateTime toLocalDateTime(Timestamp timestamp) { - if (timestamp == null) { - return null; - } - return timestamp.toLocalDateTime(); + @Override + public Optional findById(Long id) { + String sql = "select id, user_id, password, name, email, created_at, updated_at from ns_user where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new NsUser( + rs.getLong(1), + rs.getString(2), + rs.getString(3), + rs.getString(4), + rs.getString(5), + toLocalDateTime(rs.getTimestamp(6)), + toLocalDateTime(rs.getTimestamp(7))); + return Optional.of(jdbcTemplate.queryForObject(sql, rowMapper, id)); } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 8d5a988c8..80cf6a69a 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -48,3 +48,43 @@ create table delete_history ( deleted_by_id bigint, primary key (id) ); + +create table image ( + id bigint generated by default as identity, + size bigint not null, + width int not null, + height int not null, + type varchar(10) not null, + primary key (id) +); + +create table session ( + id bigint generated by default as identity, + course_id bigint not null, + image_id bigint not null, + title varchar(255) not null, + start_at DATETIME not null, + end_at DATETIME not null, + state varchar(10) not null, + fee_type varchar(10) not null, + session_fee bigint null, + available_count bigint null, + remain_count bigint null, + created_at timestamp not null, + updated_at timestamp, + primary key (id), + + foreign key (image_id) references image (id), + foreign key (course_id) references course (id) +); + +create table enrollment ( + id bigint generated by default as identity, + user_id bigint not null, + session_id bigint not null, + created_at timestamp not null, + updated_at timestamp, + primary key (id), + foreign key (session_id) references session (id), + foreign key (user_id) references ns_user (id) +); diff --git a/src/test/java/nextstep/courses/domain/image/CoverImageTest.java b/src/test/java/nextstep/courses/domain/image/CoverImageTest.java index 2edcd5260..469dae0f8 100644 --- a/src/test/java/nextstep/courses/domain/image/CoverImageTest.java +++ b/src/test/java/nextstep/courses/domain/image/CoverImageTest.java @@ -10,7 +10,7 @@ class CoverImageTest { @Test void validateSizeTest() { assertThatThrownBy(() -> { - new CoverImage(1024L * 1024L * 3, 300, 200, ImageType.GIF); + new CoverImage(300, 200, 1024L * 1024L * 3, ImageType.GIF); }).isInstanceOf(IllegalArgumentException.class); } diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index b95671a1d..34c636e80 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -20,30 +20,30 @@ class SessionTest { @BeforeEach void setUp() { sessionPeriod = new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(1)); - coverImage = new CoverImage(3000L, 300, 200, ImageType.SVG); - freeSession = new FreeSession(1L, "name", sessionPeriod, SessionState.FINISHED, null); + coverImage = new CoverImage(300, 200, 3000L, ImageType.SVG); + freeSession = new Session(1L, "name", sessionPeriod, SessionState.FINISHED, coverImage, SessionFeeType.FREE, null, new EnrollmentCount(1), LocalDateTime.now(), null); } @Test void validateSessionStateTest() { assertThatThrownBy(() -> { - freeSession.enroll(new Payment("id", 1L, JAVAJIGI.getId(), 0L)); + freeSession.validateEnrollState(new Payment("id", 1L, JAVAJIGI.getId(), 0L)); }).isInstanceOf(IllegalArgumentException.class); } @Test void checkEnrollmentAvailableTest_isNoRemaining() { - paidSession = new PaidSession(1L, "name", sessionPeriod, SessionState.RECRUITING, coverImage, new SessionFee(1000L), 0); + paidSession = new Session(1L, "name", sessionPeriod, SessionState.RECRUITING, coverImage, SessionFeeType.PAID, new SessionFee(1000L), new EnrollmentCount(0), LocalDateTime.now(), null); assertThatThrownBy(() -> { - paidSession.enroll(new Payment("id", 1L, JAVAJIGI.getId(), 1000L)); + paidSession.validateEnrollState(new Payment("id", 1L, JAVAJIGI.getId(), 1000L)); }).isInstanceOf(IllegalArgumentException.class); } @Test void checkEnrollmentAvailableTest_isNotEqualToFee() { - paidSession = new PaidSession(1L, "name", sessionPeriod, SessionState.RECRUITING, coverImage, new SessionFee(1000L), 1); + paidSession = new Session(1L, "name", sessionPeriod, SessionState.RECRUITING, coverImage, SessionFeeType.PAID, new SessionFee(1000L), new EnrollmentCount(1), LocalDateTime.now(), null); assertThatThrownBy(() -> { - paidSession.enroll(new Payment("id", 1L, JAVAJIGI.getId(), 2000L)); + paidSession.validateEnrollState(new Payment("id", 1L, JAVAJIGI.getId(), 2000L)); }).isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index f087fc0ad..1b367303a 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -29,9 +29,8 @@ void setUp() { @Test void crud() { Course course = new Course("TDD, 클린 코드 with Java", 1L); - int count = courseRepository.save(course); - assertThat(count).isEqualTo(1); - Course savedCourse = courseRepository.findById(1L); + long courseId = courseRepository.save(course); + Course savedCourse = courseRepository.findById(courseId).get(); assertThat(course.getTitle()).isEqualTo(savedCourse.getTitle()); LOGGER.debug("Course: {}", savedCourse); } diff --git a/src/test/java/nextstep/courses/infrastructure/JdbcEnrollmentRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/JdbcEnrollmentRepositoryTest.java new file mode 100644 index 000000000..66a3e9274 --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/JdbcEnrollmentRepositoryTest.java @@ -0,0 +1,73 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.CourseRepository; +import nextstep.courses.domain.Enrollment; +import nextstep.courses.domain.EnrollmentRepository; +import nextstep.courses.domain.image.CoverImage; +import nextstep.courses.domain.image.ImageRepository; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionFee; +import nextstep.courses.domain.session.SessionRepository; +import nextstep.courses.domain.session.SessionState; +import nextstep.courses.infrastructure.image.JdbcImageRepository; +import nextstep.courses.infrastructure.session.JdbcSessionRepository; +import nextstep.courses.mock.MockCourse; +import nextstep.users.domain.NsUser; +import nextstep.users.domain.UserRepository; +import nextstep.users.infrastructure.JdbcUserRepository; +import org.junit.jupiter.api.BeforeEach; +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 nextstep.users.domain.NsUserTest.JAVAJIGI; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@JdbcTest +public class JdbcEnrollmentRepositoryTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + private EnrollmentRepository enrollmentRepository; + private SessionRepository sessionRepository; + private CourseRepository courseRepository; + private ImageRepository imageRepository; + private UserRepository userRepository; + + @BeforeEach + void setUp() { + enrollmentRepository = new JdbcEnrollmentRepository(jdbcTemplate); + sessionRepository = new JdbcSessionRepository(jdbcTemplate); + courseRepository = new JdbcCourseRepository(jdbcTemplate); + imageRepository = new JdbcImageRepository(jdbcTemplate); + userRepository = new JdbcUserRepository(jdbcTemplate); + } + + @Test + void saveAndFindTest() { + // given + long courseId = courseRepository.save(MockCourse.getMockCourse()); + long imageId = imageRepository.save(MockCourse.getMockCoverImage()); + CoverImage coverImage = imageRepository.findById(imageId).get(); + + Session session = new Session("sessionTitle", + MockCourse.getMockSessionPeriod(), + SessionState.PREPARING, + coverImage, + SessionFee.ZERO(), + null); + long sessionId = sessionRepository.save(courseId, session); + NsUser nsUser = userRepository.findByUserId(JAVAJIGI.getUserId()).get(); + + // when + Enrollment enrollment = new Enrollment(nsUser.getId(), sessionId); + long enrollmentId = enrollmentRepository.save(enrollment); + Enrollment enrollmentResult = enrollmentRepository.findById(enrollmentId).get(); + + // then + assertThat(enrollmentResult.nsUserId()).isEqualTo(1L); + assertThat(enrollmentResult.sessionId()).isEqualTo(sessionId); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/image/JdbcImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/image/JdbcImageRepositoryTest.java new file mode 100644 index 000000000..7b403b98d --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/image/JdbcImageRepositoryTest.java @@ -0,0 +1,41 @@ +package nextstep.courses.infrastructure.image; + +import nextstep.courses.domain.image.CoverImage; +import nextstep.courses.domain.image.ImageRepository; +import nextstep.courses.domain.image.ImageType; +import org.junit.jupiter.api.BeforeEach; +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.assertj.core.api.AssertionsForClassTypes.assertThat; + +@JdbcTest +class JdbcImageRepositoryTest { + @Autowired + private JdbcTemplate jdbcTemplate; + + private ImageRepository imageRepository; + + @BeforeEach + void setUp() { + imageRepository = new JdbcImageRepository(jdbcTemplate); + } + + @Test + void saveAndFindTest() { + // given + int width = 300; + int height = 200; + long size = 500L; + CoverImage coverImage = new CoverImage(width, height, size, ImageType.GIF); + // when + long id = imageRepository.save(coverImage); + CoverImage imageResult = imageRepository.findById(id).get(); + // then + assertThat(imageResult.getWidth()).isEqualTo(width); + assertThat(imageResult.getHeight()).isEqualTo(height); + assertThat(imageResult.getSize()).isEqualTo(size); + } +} diff --git a/src/test/java/nextstep/courses/infrastructure/session/JdbcSessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/session/JdbcSessionRepositoryTest.java new file mode 100644 index 000000000..7baf5a8b3 --- /dev/null +++ b/src/test/java/nextstep/courses/infrastructure/session/JdbcSessionRepositoryTest.java @@ -0,0 +1,55 @@ +package nextstep.courses.infrastructure.session; + +import nextstep.courses.domain.CourseRepository; +import nextstep.courses.domain.image.CoverImage; +import nextstep.courses.domain.image.ImageRepository; +import nextstep.courses.domain.session.EnrollmentCount; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionFee; +import nextstep.courses.domain.session.SessionRepository; +import nextstep.courses.domain.session.SessionState; +import nextstep.courses.infrastructure.JdbcCourseRepository; +import nextstep.courses.infrastructure.image.JdbcImageRepository; +import nextstep.courses.mock.MockCourse; +import org.junit.jupiter.api.BeforeEach; +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.assertj.core.api.AssertionsForClassTypes.assertThat; + +@JdbcTest +class JdbcSessionRepositoryTest { + @Autowired + private JdbcTemplate jdbcTemplate; + + private SessionRepository sessionRepository; + + private CourseRepository courseRepository; + private ImageRepository imageRepository; + + @BeforeEach + void setUp() { + sessionRepository = new JdbcSessionRepository(jdbcTemplate); + courseRepository = new JdbcCourseRepository(jdbcTemplate); + imageRepository = new JdbcImageRepository(jdbcTemplate); + } + + @Test + void saveAndFindTest() { + // given + long courseId = courseRepository.save(MockCourse.getMockCourse()); + long imageId = imageRepository.save(MockCourse.getMockCoverImage()); + CoverImage coverImage = imageRepository.findById(imageId).get(); + + // when + Session session = new Session("sessionTitle", MockCourse.getMockSessionPeriod(), SessionState.PREPARING, coverImage, SessionFee.of(100L), new EnrollmentCount(20)); + long sessionId = sessionRepository.save(courseId, session); + Session sessionResult = sessionRepository.findById(sessionId).get(); + // then + assertThat(sessionResult.getTitle()).isEqualTo("sessionTitle"); + assertThat(sessionResult.getSessionState()).isEqualTo(SessionState.PREPARING); + assertThat(sessionResult.getSessionFee()).isEqualTo(SessionFee.of(100L)); + } +} diff --git a/src/test/java/nextstep/courses/mock/MockCourse.java b/src/test/java/nextstep/courses/mock/MockCourse.java new file mode 100644 index 000000000..2e334de0b --- /dev/null +++ b/src/test/java/nextstep/courses/mock/MockCourse.java @@ -0,0 +1,22 @@ +package nextstep.courses.mock; + +import nextstep.courses.domain.Course; +import nextstep.courses.domain.image.CoverImage; +import nextstep.courses.domain.image.ImageType; +import nextstep.courses.domain.session.SessionPeriod; + +import java.time.LocalDateTime; + +public class MockCourse { + public static CoverImage getMockCoverImage() { + return new CoverImage(300, 200, 500L, ImageType.GIF); + } + + public static SessionPeriod getMockSessionPeriod() { + return new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(1)); + } + + public static Course getMockCourse() { + return new Course("courseTitle", 1L); + } +} From 8ba89986100036d88d25dd76d223a30be15a94ad Mon Sep 17 00:00:00 2001 From: h3yon Date: Sat, 9 Dec 2023 20:19:45 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feature:=202=EB=8B=A8=EA=B3=84=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courses/domain/image/CoverImage.java | 4 +++- .../courses/domain/session/EnrollmentCount.java | 17 +++-------------- .../session/JdbcSessionRepository.java | 17 ++++++++--------- src/main/resources/schema.sql | 1 - 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/main/java/nextstep/courses/domain/image/CoverImage.java b/src/main/java/nextstep/courses/domain/image/CoverImage.java index d29ad91c8..3c67c1a62 100644 --- a/src/main/java/nextstep/courses/domain/image/CoverImage.java +++ b/src/main/java/nextstep/courses/domain/image/CoverImage.java @@ -8,6 +8,8 @@ public class CoverImage { private static final int MIN_WIDTH = 300; private static final int MIN_HEIGHT = 200; private static final Long MAX_SIZE = 1024L * 1024L; + private static final int WIDTH_RATIO = 3; + private static final int HEIGHT_RATIO = 2; private final Long id; private final int width; @@ -42,7 +44,7 @@ private void validateSize(final long size) { } private void validateRatio(final int width, final int height) { - if (width * 2 != height * 3) { + if (width * HEIGHT_RATIO != height * WIDTH_RATIO) { throw new IllegalArgumentException(INVALID_IMAGE_RATIO_MESSAGE); } } diff --git a/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java b/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java index 845acefc2..c34abdd34 100644 --- a/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java +++ b/src/main/java/nextstep/courses/domain/session/EnrollmentCount.java @@ -1,32 +1,21 @@ package nextstep.courses.domain.session; public class EnrollmentCount { - private final int availableCount; - private int remainCount; + private int availableCount; public EnrollmentCount(final int availableCount) { this.availableCount = availableCount; - this.remainCount = availableCount; - } - - public EnrollmentCount(final int maxEnrollmentCount, final int remainEnrollmentCount) { - this.availableCount = maxEnrollmentCount; - this.remainCount = remainEnrollmentCount; } public boolean isNoRemaining() { - return remainCount <= 0; + return availableCount < 1; } public void decrease() { - this.remainCount--; + this.availableCount--; } public int getAvailableCount() { return availableCount; } - - public int getRemainCount() { - return remainCount; - } } diff --git a/src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java index c6f1a6e1a..ab9baca81 100644 --- a/src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/session/JdbcSessionRepository.java @@ -36,12 +36,12 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public long save(Long courseId, Session session) { String sql = "insert into session (title, start_at, end_at, state, course_id, image_id, fee_type, " + - " session_fee, available_count, remain_count, created_at) " + - " values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + " session_fee, available_count, created_at) " + + " values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; SessionPeriod sessionPeriod = session.getSessionPeriod(); EnrollmentCount enrollmentCount = (session.getSessionFeeType() == SessionFeeType.PAID) ? session.getEnrollmentCount() - : new EnrollmentCount(0, 0); + : new EnrollmentCount(0); KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(con -> { @@ -55,8 +55,7 @@ public long save(Long courseId, Session session) { ps.setString(7, session.getSessionFeeType().name()); ps.setLong(8, session.getSessionFee().getFee()); ps.setInt(9, enrollmentCount.getAvailableCount()); - ps.setInt(10, enrollmentCount.getRemainCount()); - ps.setTimestamp(11, toTimeStamp(LocalDateTime.now())); + ps.setTimestamp(10, toTimeStamp(LocalDateTime.now())); return ps; }, keyHolder); @@ -67,7 +66,7 @@ public long save(Long courseId, Session session) { @Override public Optional findById(final Long id) { String sql = "select id, title, start_at, end_at, state, course_id, image_id, fee_type," + - " session_fee, available_count, remain_count, created_at, updated_at from session where id = ?"; + " session_fee, available_count, created_at, updated_at from session where id = ?"; RowMapper rowMapper = (rs, rowNum) -> new Session( rs.getLong(1), rs.getString(2), @@ -76,9 +75,9 @@ public Optional findById(final Long id) { findByCoverImage(rs.getLong(7)), SessionFeeType.valueOf(rs.getString(8)), SessionFee.of(rs.getLong(9)), - new EnrollmentCount(rs.getInt(10), rs.getInt(11)), - toLocalDateTime(rs.getTimestamp(12)), - toLocalDateTime(rs.getTimestamp(13))); + new EnrollmentCount(rs.getInt(10)), + toLocalDateTime(rs.getTimestamp(11)), + toLocalDateTime(rs.getTimestamp(12))); return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, id)); } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 80cf6a69a..b260ad541 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -69,7 +69,6 @@ create table session ( fee_type varchar(10) not null, session_fee bigint null, available_count bigint null, - remain_count bigint null, created_at timestamp not null, updated_at timestamp, primary key (id),