diff --git a/src/main/java/nextstep/courses/domain/Capacity.java b/src/main/java/nextstep/courses/domain/Capacity.java index 57778f334b..25479c4e0d 100644 --- a/src/main/java/nextstep/courses/domain/Capacity.java +++ b/src/main/java/nextstep/courses/domain/Capacity.java @@ -17,4 +17,8 @@ private void validation(int capacity) { public boolean isFull(int enrolledCount) { return capacity <= enrolledCount; } + + public int getCapacity() { + return capacity; + } } diff --git a/src/main/java/nextstep/courses/domain/Enrollment.java b/src/main/java/nextstep/courses/domain/Enrollment.java index 9c261962a0..1278be86ee 100644 --- a/src/main/java/nextstep/courses/domain/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/Enrollment.java @@ -3,52 +3,56 @@ import java.util.Objects; public class Enrollment { + private final Long id; private final Long studentId; private final Long sessionId; - private final Money money; - public Enrollment(Long studentId, Long sessionId, int money) { - this(studentId, sessionId, new Money(money)); + public Enrollment(Long studentId, Long sessionId) { + this(1L, studentId, sessionId); } - public Enrollment(Long studentId, Long sessionId, Money money) { + public Enrollment(Long id, Long studentId, Long sessionId) { + this.id = id; this.studentId = studentId; this.sessionId = sessionId; - this.money = money; - } - - public Money getMoney() { - return money; } public boolean sameSession(Long id) { return this.sessionId.equals(id); } - public void validateBelongsTo(Session session) { - if (!sameSession(session.getId())) { + public void validateBelongsTo(Long sessionId) { + if (!sameSession(sessionId)) { throw new IllegalArgumentException("신청하고자 하는 강의가 아닙니다."); } } + public Long getStudentId() { + return studentId; + } + + public Long getSessionId() { + return sessionId; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Enrollment that = (Enrollment) o; - return Objects.equals(studentId, that.studentId) && Objects.equals(sessionId, that.sessionId) && Objects.equals(money, that.money); + return Objects.equals(id, that.id) && Objects.equals(studentId, that.studentId) && Objects.equals(sessionId, that.sessionId); } @Override public int hashCode() { - return Objects.hash(studentId, sessionId, money); + return Objects.hash(id, studentId, sessionId); } @Override public String toString() { return "Enrollment{" + - "studentId=" + studentId + + "id=" + id + + ", studentId=" + studentId + ", sessionId=" + sessionId + - ", money=" + money + '}'; } } diff --git a/src/main/java/nextstep/courses/domain/EnrollmentRule.java b/src/main/java/nextstep/courses/domain/EnrollmentRule.java index 388c7712ad..d2399ad61c 100644 --- a/src/main/java/nextstep/courses/domain/EnrollmentRule.java +++ b/src/main/java/nextstep/courses/domain/EnrollmentRule.java @@ -1,7 +1,7 @@ package nextstep.courses.domain; public interface EnrollmentRule { - void validate(int money, int enrolledCount); + void validate(Money money, int enrolledCount); void validateMoney(Money money); diff --git a/src/main/java/nextstep/courses/domain/FreeEnrollmentRule.java b/src/main/java/nextstep/courses/domain/FreeEnrollmentRule.java index e5a7d51c87..a97102702f 100644 --- a/src/main/java/nextstep/courses/domain/FreeEnrollmentRule.java +++ b/src/main/java/nextstep/courses/domain/FreeEnrollmentRule.java @@ -1,8 +1,13 @@ package nextstep.courses.domain; public class FreeEnrollmentRule implements EnrollmentRule { + + public FreeEnrollmentRule() { + + } + @Override - public void validate(int money, int enrolledCount) { + public void validate(Money money, int enrolledCount) { // 무료 강의는 아무 제한 없음 } diff --git a/src/main/java/nextstep/courses/domain/ImageFile.java b/src/main/java/nextstep/courses/domain/ImageFile.java index 065344256f..26e4c57d1e 100644 --- a/src/main/java/nextstep/courses/domain/ImageFile.java +++ b/src/main/java/nextstep/courses/domain/ImageFile.java @@ -1,5 +1,7 @@ package nextstep.courses.domain; +import java.util.Objects; + public class ImageFile { private static final long MAX_SIZE = 1024 * 1024; // 1MB private static final int MIN_WIDTH_PX = 300; @@ -7,29 +9,51 @@ public class ImageFile { private static final int WIDTH_RATIO = 3; private static final int HEIGHT_RATIO = 2; - private long size; - private ImageType imageType; - private int width; - private int height; + private Long id; + private final long size; + private final ImageType imageType; + private final int width; + private final int height; public ImageFile(long size) { - this(size, "png", 300, 200); + this(1L, size, "png", MIN_WIDTH_PX , MIN_HEIGHT_PX); } public ImageFile(long size, String imageType, int width, int height) { + this(1L, size, imageType, width, height); + } + + public ImageFile(Long id, long size, String imageType, int width, int height) { validateSize(size); validateDimensions(width, height); ImageType type = ImageType.getType(imageType); validateImageType(type); + this.id = id; this.size = size; this.imageType = type; this.width = width; this.height = height; } + public long getSize() { + return this.size; + } + + public ImageType getImageType() { + return this.imageType; + } + + public int getWidth() { + return this.width; + } + + public int getHeight() { + return this.height; + } + private void validateImageType(ImageType type) { if (type == ImageType.UNKNOWN) { throw new IllegalArgumentException("지원하지 않는 이미지 타입입니다. (허용 형식: gif, jpg/jpeg, png, svg)"); @@ -61,4 +85,31 @@ private void validateSize(long size) { throw new IllegalArgumentException("이미지 파일 크기는 1MB 이하여야 합니다. (현재: " + size + ")"); } } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + ImageFile imageFile = (ImageFile) o; + return size == imageFile.size && width == imageFile.width && height == imageFile.height && Objects.equals(id, imageFile.id) && imageType == imageFile.imageType; + } + + @Override + public int hashCode() { + return Objects.hash(id, size, imageType, width, height); + } + + @Override + public String toString() { + return "ImageFile{" + + "id=" + id + + ", size=" + size + + ", imageType=" + imageType + + ", width=" + width + + ", height=" + height + + '}'; + } + + public Long getImageId() { + return this.id; + } } diff --git a/src/main/java/nextstep/courses/domain/Money.java b/src/main/java/nextstep/courses/domain/Money.java index 568a89f7d9..7edb8b6209 100644 --- a/src/main/java/nextstep/courses/domain/Money.java +++ b/src/main/java/nextstep/courses/domain/Money.java @@ -3,7 +3,9 @@ import java.util.Objects; public class Money { - private int money; + public static final Money ZERO = new Money(0); + + private final int money; public Money(int money) { validation(money); @@ -21,7 +23,7 @@ public boolean matches(int money) { } public boolean matches(Money money) { - return this.money == money.getMoney(); + return matches(money.getMoney()); } public int getMoney() { diff --git a/src/main/java/nextstep/courses/domain/PaidEnrollmentRule.java b/src/main/java/nextstep/courses/domain/PaidEnrollmentRule.java index a944d48bdc..8d77456fa7 100644 --- a/src/main/java/nextstep/courses/domain/PaidEnrollmentRule.java +++ b/src/main/java/nextstep/courses/domain/PaidEnrollmentRule.java @@ -4,18 +4,18 @@ public class PaidEnrollmentRule implements EnrollmentRule { private final Money price; private final Capacity capacity; - PaidEnrollmentRule(int price, int capacity) { + public PaidEnrollmentRule(int price, int capacity) { this(new Money(price), new Capacity(capacity)); } - PaidEnrollmentRule(Money price, Capacity capacity) { + public PaidEnrollmentRule(Money price, Capacity capacity) { this.price = price; this.capacity = capacity; } @Override - public void validate(int money, int enrolledCount) { - validateMoney(new Money(money)); + public void validate(Money money, int enrolledCount) { + validateMoney(money); validateCapacity(enrolledCount); } @@ -37,4 +37,12 @@ public void validateCapacity(int enrolledCount) { public SessionType getType() { return SessionType.PAID; } + + public int getPrice() { + return this.price.getMoney(); + } + + public int getCapacity() { + return this.capacity.getCapacity(); + } } diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index bf22d963a6..4ba90de398 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -1,5 +1,8 @@ package nextstep.courses.domain; +import java.time.LocalDateTime; +import java.util.Objects; + public class Session { private final Long id; private final ImageFile imageFile; @@ -8,8 +11,16 @@ public class Session { private final EnrollmentRule enrollmentRule; private final Enrollments enrollments; - public Session(Long id, ImageFile imageFile, SessionPeriod period, SessionStatus sessionStatus, EnrollmentRule enrollmentRule) { - this(id, imageFile, period, sessionStatus, enrollmentRule, new Enrollments()); + public Session(Long id,ImageFile imageFile, LocalDateTime startTime, LocalDateTime endTime, String sessionStatus, Integer price, Integer capacity) { + this(id, imageFile, new SessionPeriod(startTime, endTime), SessionStatus.valueOf(sessionStatus), allocateEnrollmentRule(price, capacity), new Enrollments()); + } + + public Session(ImageFile imageFile, SessionPeriod period, SessionStatus sessionStatus, EnrollmentRule enrollmentRule) { + this(null, imageFile, period, sessionStatus, enrollmentRule, new Enrollments()); + } + + public Session(ImageFile imageFile, SessionPeriod period, SessionStatus sessionStatus, EnrollmentRule enrollmentRule, Enrollments enrollments) { + this(null, imageFile, period, sessionStatus, enrollmentRule, enrollments); } public Session(Long id, ImageFile imageFile, SessionPeriod period, SessionStatus sessionStatus, EnrollmentRule enrollmentRule, Enrollments enrollments) { @@ -25,13 +36,12 @@ public int countEnrollments() { return enrollments.countEnrollments(); } - public void enroll(Enrollment enrollment) { + public void enroll(Enrollment enrollment, Money money) { validationRecruiting(); - enrollment.validateBelongsTo(this); + enrollment.validateBelongsTo(getId()); - enrollmentRule.validateMoney(enrollment.getMoney()); - enrollmentRule.validateCapacity(enrollments.countEnrollments()); + enrollmentRule.validate(money, countEnrollments()); enrollments.enroll(enrollment); } @@ -40,9 +50,77 @@ public Long getId() { return id; } + public Long getImageId() { + return this.imageFile.getImageId(); + } + + public String getSessionStatus() { + return this.sessionStatus.toString(); + } + + public Integer getPrice() { + if (enrollmentRule.getType().equals(SessionType.PAID)) { + return ((PaidEnrollmentRule) this.enrollmentRule).getPrice(); + } + + return null; + } + + public Integer getCapacity() { + if (enrollmentRule.getType().equals(SessionType.PAID)) { + return ((PaidEnrollmentRule) this.enrollmentRule).getCapacity(); + } + + return null; + } + + public LocalDateTime getStartTime() { + return this.period.getStartTime(); + } + + public LocalDateTime getEndTime() { + return this.period.getEndTime(); + } + + public SessionPeriod getPeriod() { + return this.period; + } + + private static EnrollmentRule allocateEnrollmentRule(Integer price, Integer capacity) { + if (price != null) { + return new PaidEnrollmentRule(price, capacity); + } + + return new FreeEnrollmentRule(); + } + private void validationRecruiting() { if (!sessionStatus.enableRecruiting()) { throw new IllegalArgumentException("모집중인 강의만 수강 신청할 수 있습니다."); } } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Session session = (Session) o; + return Objects.equals(id, session.id) && Objects.equals(imageFile, session.imageFile) && Objects.equals(period, session.period) && sessionStatus == session.sessionStatus && Objects.equals(enrollmentRule, session.enrollmentRule) && Objects.equals(enrollments, session.enrollments); + } + + @Override + public int hashCode() { + return Objects.hash(id, imageFile, period, sessionStatus, enrollmentRule, enrollments); + } + + @Override + public String toString() { + return "Session{" + + "id=" + id + + ", imageFile=" + imageFile + + ", period=" + period + + ", sessionStatus=" + sessionStatus + + ", enrollmentRule=" + enrollmentRule + + ", enrollments=" + enrollments + + '}'; + } } diff --git a/src/main/java/nextstep/courses/domain/SessionPeriod.java b/src/main/java/nextstep/courses/domain/SessionPeriod.java index c62dfd6b22..8530503489 100644 --- a/src/main/java/nextstep/courses/domain/SessionPeriod.java +++ b/src/main/java/nextstep/courses/domain/SessionPeriod.java @@ -1,6 +1,7 @@ package nextstep.courses.domain; import java.time.LocalDateTime; +import java.util.Objects; public class SessionPeriod { private LocalDateTime startTime; @@ -12,6 +13,14 @@ public SessionPeriod(LocalDateTime startTime, LocalDateTime endTime) { this.endTime = endTime; } + public LocalDateTime getStartTime() { + return this.startTime; + } + + public LocalDateTime getEndTime() { + return this.endTime; + } + private void validationPeriod(LocalDateTime startTime, LocalDateTime endTime) { if (startTime == null || endTime == null) { throw new IllegalArgumentException("시작 시간과 종료 시간은 필수입니다."); @@ -21,4 +30,24 @@ private void validationPeriod(LocalDateTime startTime, LocalDateTime endTime) { throw new IllegalArgumentException("종료 시간은 시작 시간 이후여야 합니다."); } } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + SessionPeriod that = (SessionPeriod) o; + return Objects.equals(startTime, that.startTime) && Objects.equals(endTime, that.endTime); + } + + @Override + public int hashCode() { + return Objects.hash(startTime, endTime); + } + + @Override + public String toString() { + return "SessionPeriod{" + + "startTime=" + startTime + + ", endTime=" + endTime + + '}'; + } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java index f9122cbe33..79409f765e 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.repository.CourseRepository; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; 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 0000000000..f87ca01a1b --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcEnrollmentRepository.java @@ -0,0 +1,66 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.Enrollment; +import nextstep.courses.repository.EnrollmentRepository; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.PreparedStatementCreator; +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.List; + +@Repository("enrollmentRepository") +public class JdbcEnrollmentRepository implements EnrollmentRepository { + + private JdbcOperations jdbcTemplate; + + public JdbcEnrollmentRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Long save(Enrollment enrollment) { + String sql = "insert into enrollment (student_id, session_id) values (?, ?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(connect -> { + PreparedStatement ps = connect.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, enrollment.getStudentId()); + ps.setLong(2, enrollment.getSessionId()); + return ps; + }, keyHolder); + + return keyHolder.getKey().longValue(); + } + + @Override + public Enrollment findById(Long id) { + String sql = "select * from enrollment where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new Enrollment( + rs.getLong("id"), + rs.getLong("student_id"), + rs.getLong("session_id") + ); + + return jdbcTemplate.queryForObject(sql, rowMapper, id); + } + + @Override + public List findBySessionId(Long sessionId) { + String sql = "select * from enrollment where session_id = ?"; + + RowMapper rowMapper = (rs, rowNum) -> new Enrollment( + rs.getLong("id"), + rs.getLong("student_id"), + rs.getLong("session_id") + ); + + return jdbcTemplate.query(sql, rowMapper, sessionId); + } + + +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcImageFileRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcImageFileRepository.java new file mode 100644 index 0000000000..9debcdde10 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcImageFileRepository.java @@ -0,0 +1,53 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.ImageFile; +import nextstep.courses.repository.ImageFileRepository; +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; + +@Repository("imageFileRepository") +public class JdbcImageFileRepository implements ImageFileRepository { + + private JdbcOperations jdbcTemplate; + + public JdbcImageFileRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Long save(ImageFile imageFile) { + String sql = "insert into image_file (size, image_type, width, height) values (?, ?, ?, ?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(connect -> { + PreparedStatement ps = connect.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, imageFile.getSize()); + ps.setString(2, imageFile.getImageType().toString()); + ps.setInt(3, imageFile.getWidth()); + ps.setInt(4, imageFile.getHeight()); + return ps; + }, keyHolder); + + return keyHolder.getKey().longValue(); + } + + @Override + public ImageFile findById(long id) { + String sql = "select * from image_file where id = ?"; + RowMapper rowMapper = (rs, rowNum) -> new ImageFile( + rs.getLong("id"), + rs.getLong("size"), + rs.getString("image_type"), + rs.getInt("width"), + rs.getInt("height") + ); + + return jdbcTemplate.queryForObject(sql, rowMapper, id); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java new file mode 100644 index 0000000000..5dc487b1e7 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -0,0 +1,107 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.*; +import nextstep.courses.repository.SessionRepository; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDateTime; + + +@Repository("sessionRepository") +public class JdbcSessionRepository implements SessionRepository { + + private JdbcOperations jdbcTemplate; + + public JdbcSessionRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Long save(Session session) { + String sql = "insert into session (image_id, session_status, price, capacity, start_time, end_time) values (?, ?, ?, ?, ?, ?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(connect -> { + PreparedStatement ps = connect.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, session.getImageId()); + ps.setString(2 , session.getSessionStatus()); + ps.setInt(3 , session.getPrice()); + ps.setInt(4 , session.getCapacity()); + extractedPrice(session, ps); + extractedCapacity(session, ps); + ps.setTimestamp(5, Timestamp.valueOf(session.getStartTime())); + ps.setTimestamp(6, Timestamp.valueOf(session.getEndTime())); + return ps; + }, keyHolder); + + return keyHolder.getKey().longValue(); + } + + private static void extractedCapacity(Session session, PreparedStatement ps) throws SQLException { + if (session.getCapacity() != null) { + ps.setInt(4, session.getCapacity()); + } else { + ps.setNull(4, Types.INTEGER); + } + } + + private static void extractedPrice(Session session, PreparedStatement ps) throws SQLException { + if (session.getPrice() != null) { + ps.setInt(3, session.getPrice()); + } else { + ps.setNull(3, Types.INTEGER); + } + } + + public Session findById(long id) { + String sql = "select " + + "s.id, " + + "s.session_status, " + + "s.price, " + + "s.capacity, " + + "s.start_time, " + + "s.end_time, " + + "i.id AS image_id, " + + "i.size, " + + "i.image_type, " + + "i.width, " + + "i.height " + + "from session s " + + "join image_file i " + + "on s.image_id = i.id " + + "where s.id = ?"; + + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { + + ImageFile imageFile = new ImageFile( + rs.getLong("image_id"), + rs.getLong("size"), + rs.getString("image_type"), + rs.getInt("width"), + rs.getInt("height") + ); + + Session session = new Session( + rs.getLong("id"), + imageFile, + rs.getObject("start_time", LocalDateTime.class), + rs.getObject("end_time", LocalDateTime.class), + rs.getString("session_status"), + rs.getObject("price", Integer.class), + rs.getObject("capacity", Integer.class) + ); + + return session; + }, id); + } + + +} diff --git a/src/main/java/nextstep/courses/domain/CourseRepository.java b/src/main/java/nextstep/courses/repository/CourseRepository.java similarity index 56% rename from src/main/java/nextstep/courses/domain/CourseRepository.java rename to src/main/java/nextstep/courses/repository/CourseRepository.java index 6aaeb638d1..0680a57a23 100644 --- a/src/main/java/nextstep/courses/domain/CourseRepository.java +++ b/src/main/java/nextstep/courses/repository/CourseRepository.java @@ -1,4 +1,6 @@ -package nextstep.courses.domain; +package nextstep.courses.repository; + +import nextstep.courses.domain.Course; public interface CourseRepository { int save(Course course); diff --git a/src/main/java/nextstep/courses/repository/EnrollmentRepository.java b/src/main/java/nextstep/courses/repository/EnrollmentRepository.java new file mode 100644 index 0000000000..95a700cde6 --- /dev/null +++ b/src/main/java/nextstep/courses/repository/EnrollmentRepository.java @@ -0,0 +1,13 @@ +package nextstep.courses.repository; + +import nextstep.courses.domain.Enrollment; + +import java.util.List; + +public interface EnrollmentRepository { + Long save(Enrollment enrollment); + + Enrollment findById(Long id); + + List findBySessionId(Long sessionId); +} diff --git a/src/main/java/nextstep/courses/repository/ImageFileRepository.java b/src/main/java/nextstep/courses/repository/ImageFileRepository.java new file mode 100644 index 0000000000..2606fff38d --- /dev/null +++ b/src/main/java/nextstep/courses/repository/ImageFileRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.repository; + +import nextstep.courses.domain.ImageFile; + +public interface ImageFileRepository { + Long save(ImageFile imageFile); + + ImageFile findById(long id); +} diff --git a/src/main/java/nextstep/courses/repository/SessionRepository.java b/src/main/java/nextstep/courses/repository/SessionRepository.java new file mode 100644 index 0000000000..4ae0de287a --- /dev/null +++ b/src/main/java/nextstep/courses/repository/SessionRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.repository; + + +import nextstep.courses.domain.Session; + +public interface SessionRepository { + Long save(Session session); + Session findById(long id); +} diff --git a/src/main/java/nextstep/courses/service/SessionService.java b/src/main/java/nextstep/courses/service/SessionService.java new file mode 100644 index 0000000000..070dc3ac9c --- /dev/null +++ b/src/main/java/nextstep/courses/service/SessionService.java @@ -0,0 +1,53 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.*; +import nextstep.courses.repository.EnrollmentRepository; +import nextstep.courses.repository.ImageFileRepository; +import nextstep.courses.repository.SessionRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SessionService { + + private final SessionRepository sessionRepository; + private final ImageFileRepository imageFileRepository; + private final EnrollmentRepository enrollmentRepository; + + public SessionService(SessionRepository sessionRepository, + ImageFileRepository imageFileRepository, + EnrollmentRepository enrollmentRepository) { + this.sessionRepository = sessionRepository; + this.imageFileRepository = imageFileRepository; + this.enrollmentRepository = enrollmentRepository; + } + + @Transactional + public Long createSession(ImageFile imageFile, + SessionPeriod period, + SessionStatus status, + EnrollmentRule enrollmentRule) { + + imageFileRepository.save(imageFile); + + Session session = new Session(imageFile, period, status, enrollmentRule); + + return sessionRepository.save(session); + } + + @Transactional(readOnly = true) + public Session findSession(Long id) { + return sessionRepository.findById(id); + } + + @Transactional + public void enroll(Long sessionId, Long memberId, Money money) { + Session session = sessionRepository.findById(sessionId); + + Enrollment enrollment = new Enrollment(memberId, sessionId); + + session.enroll(enrollment, money); + + enrollmentRepository.save(enrollment); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 8d5a988c8b..ef0f6df980 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -48,3 +48,30 @@ create table delete_history ( deleted_by_id bigint, primary key (id) ); + +create table enrollment ( + id bigint generated by default as identity, + student_id bigint, + session_id bigint, + primary key (id) +); + +create table image_file ( + id bigint generated by default as identity, + size bigint, + image_type varchar(255), + width int, + height int, + primary key (id) +); + +create table session ( + id bigint generated by default as identity, + image_id bigint not null, + session_status varchar(20) not null, + price int, + capacity int, + start_time timestamp not null, + end_time timestamp not null, + primary key (id) +) diff --git a/src/test/java/nextstep/courses/domain/PaidEnrollmentRuleTest.java b/src/test/java/nextstep/courses/domain/PaidEnrollmentRuleTest.java index 170f36bcb3..f0768196bc 100644 --- a/src/test/java/nextstep/courses/domain/PaidEnrollmentRuleTest.java +++ b/src/test/java/nextstep/courses/domain/PaidEnrollmentRuleTest.java @@ -8,6 +8,6 @@ public class PaidEnrollmentRuleTest { void 유료_강의_등록_성공() { PaidEnrollmentRule paidEnrollmentRule = new PaidEnrollmentRule(50000, 5); - paidEnrollmentRule.validate(50000,1); + paidEnrollmentRule.validate(new Money(50000),1); } } diff --git a/src/test/java/nextstep/courses/domain/SessionBuilder.java b/src/test/java/nextstep/courses/domain/SessionBuilder.java new file mode 100644 index 0000000000..4778d17670 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionBuilder.java @@ -0,0 +1,42 @@ +package nextstep.courses.domain; + +import java.time.LocalDateTime; + +public class SessionBuilder { + private Long id = 1L; + private ImageFile imageFile = new ImageFile(1024 * 1024, "png", 300 , 200); + private SessionPeriod period = + new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(7)); + private SessionStatus sessionStatus = SessionStatus.RECRUITING; + private EnrollmentRule enrollmentRule = new PaidEnrollmentRule(50000, 10); + private Enrollments enrollments = new Enrollments(); + + public static SessionBuilder aRecuitingSession() { + return new SessionBuilder(); + } + + public static SessionBuilder anEndSession() { + return new SessionBuilder() + .withStatus(SessionStatus.END); + } + + public SessionBuilder withStatus(SessionStatus status) { + this.sessionStatus = status; + return this; + } + + public SessionBuilder asPaidSession(int price, int capacity) { + this.enrollmentRule = new PaidEnrollmentRule(price, capacity); + return this; + } + + public SessionBuilder asFreeSession() { + this.enrollmentRule = new FreeEnrollmentRule(); + return this; + } + + public Session build() { + return new Session(id, imageFile, period, sessionStatus, enrollmentRule, enrollments); + } + +} diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index 3930d4c6d4..f709a40bd4 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -1,52 +1,42 @@ package nextstep.courses.domain; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SessionTest { - ImageFile imageFile; - SessionPeriod period; + @Test + void 무료_강의_등록() { + Session session = SessionBuilder.aRecuitingSession() + .asFreeSession() + .build(); - @BeforeEach - void setUp() { - imageFile = new ImageFile(1024 * 1024); - period = new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(7)); - } + session.enroll(new Enrollment(1L, 1L), Money.ZERO); + assertThat(session.countEnrollments()).isEqualTo(1); + } @Test void 강의_등록_성공() { - Session session = new Session( - 1L, - imageFile, - period, - SessionStatus.RECRUITING, - new PaidEnrollmentRule(50000, 10) - ); + Session session = SessionBuilder.aRecuitingSession() + .asPaidSession(50000, 10) + .build(); - session.enroll(new Enrollment(1L, 1L, 50000)); + session.enroll(new Enrollment(1L, 1L), new Money(50000)); assertThat(session.countEnrollments()).isEqualTo(1); } @Test void 강의_금액_불일치_예외() { - Session session = new Session( - 1L, - imageFile, - period, - SessionStatus.RECRUITING, - new PaidEnrollmentRule(50000, 10) - ); + Session session = SessionBuilder.aRecuitingSession() + .asPaidSession(50000, 10) + .build(); assertThatThrownBy( - () -> session.enroll(new Enrollment(1L, 1L, 45000)) + () -> session.enroll(new Enrollment(1L, 1L), new Money(45000)) ).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("결제 금액이 수강료와 일치하지 않습니다."); @@ -54,68 +44,52 @@ void setUp() { @Test void 마감된_강의_등록시_예외() { - Session session = new Session( - 1L, - imageFile, - period, - SessionStatus.END, - new PaidEnrollmentRule(50000, 10) - ); + Session session = SessionBuilder.anEndSession() + .asPaidSession(50000, 10) + .build(); assertThatThrownBy( - () -> session.enroll(new Enrollment(1L, 1L, 50000)) + () -> session.enroll(new Enrollment(1L, 1L), new Money(50000)) ).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("모집중인 강의만 수강 신청할 수 있습니다."); } @Test void 정원_초과_등록시_예외() { - Session session = new Session( - 1L, - imageFile, - period, - SessionStatus.RECRUITING, - new PaidEnrollmentRule(50000, 1) - ); - session.enroll(new Enrollment(1L, 1L, 50000)); + Session session = SessionBuilder.aRecuitingSession() + .asPaidSession(50000, 1) + .build(); + + session.enroll(new Enrollment(1L, 1L), new Money(50000)); assertThatThrownBy( - () -> session.enroll(new Enrollment(2L, 1L, 50000)) + () -> session.enroll(new Enrollment(2L, 1L), new Money(50000)) ).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("수강 인원이 초과되었습니다."); } @Test void 이미_수강_중인_강의_등록시_예외() { - Session session = new Session( - 1L, - imageFile, - period, - SessionStatus.RECRUITING, - new PaidEnrollmentRule(50000, 10) - ); + Session session = SessionBuilder.aRecuitingSession() + .asPaidSession(50000, 2) + .build(); - session.enroll(new Enrollment(1L, 1L, 50000)); + session.enroll(new Enrollment(1L, 1L), new Money(50000)); assertThatThrownBy( - () -> session.enroll(new Enrollment(1L, 1L, 50000)) + () -> session.enroll(new Enrollment(1L, 1L), new Money(50000)) ).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("이미 수강 신청한 강의입니다."); } @Test void 신청한_강의와_결제한_강의와_다를_경우_예외() { - Session session = new Session( - 1L, - imageFile, - period, - SessionStatus.RECRUITING, - new PaidEnrollmentRule(50000, 10) - ); - + Session session = SessionBuilder.aRecuitingSession() + .asPaidSession(50000, 2) + .build(); assertThatThrownBy( - () -> session.enroll(new Enrollment(1L, 2L, 50000)) + () -> session.enroll(new Enrollment(1L, 2L), new Money(50000)) ).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("신청하고자 하는 강의가 아닙니다."); } diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index f087fc0ad2..885346974e 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -1,7 +1,7 @@ package nextstep.courses.infrastructure; import nextstep.courses.domain.Course; -import nextstep.courses.domain.CourseRepository; +import nextstep.courses.repository.CourseRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; diff --git a/src/test/java/nextstep/courses/repository/EnrollmentRepositoryTest.java b/src/test/java/nextstep/courses/repository/EnrollmentRepositoryTest.java new file mode 100644 index 0000000000..5448cb6af4 --- /dev/null +++ b/src/test/java/nextstep/courses/repository/EnrollmentRepositoryTest.java @@ -0,0 +1,34 @@ +package nextstep.courses.repository; + +import nextstep.courses.domain.Enrollment; +import nextstep.courses.infrastructure.JdbcEnrollmentRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +@Import(JdbcEnrollmentRepository.class) +public class EnrollmentRepositoryTest { + + @Autowired + EnrollmentRepository enrollmentRepository; + + @Test + void save() { + Long enrollmentId = enrollmentRepository.save(new Enrollment(1L, 1L)); + + assertThat(enrollmentId).isNotNull(); + } + + @Test + void find() { + Long enrollmentId = enrollmentRepository.save(new Enrollment(1L, 1L)); + + Enrollment enrollment = enrollmentRepository.findById(enrollmentId); + + assertThat(enrollment).isEqualTo(new Enrollment(1L, 1L)); + } +} diff --git a/src/test/java/nextstep/courses/repository/ImageFileRepositoryTest.java b/src/test/java/nextstep/courses/repository/ImageFileRepositoryTest.java new file mode 100644 index 0000000000..af5b07e6ea --- /dev/null +++ b/src/test/java/nextstep/courses/repository/ImageFileRepositoryTest.java @@ -0,0 +1,32 @@ +package nextstep.courses.repository; + +import nextstep.courses.domain.ImageFile; +import nextstep.courses.infrastructure.JdbcImageFileRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +@Import(JdbcImageFileRepository.class) +public class ImageFileRepositoryTest { + + @Autowired + ImageFileRepository imageFileRepository; + + @Test + void save() { + Long imageId = imageFileRepository.save(new ImageFile(1024 * 1024, "jpg", 300, 200)); + + assertThat(imageId).isNotNull(); + } + + @Test + void find() { + Long imageId = imageFileRepository.save(new ImageFile(1024 * 1024, "jpg", 300, 200)); + + assertThat((imageFileRepository.findById(imageId))).isEqualTo(new ImageFile(1L,1024 * 1024, "jpg", 300, 200)); + } +} diff --git a/src/test/java/nextstep/courses/repository/SessionRepositoryTest.java b/src/test/java/nextstep/courses/repository/SessionRepositoryTest.java new file mode 100644 index 0000000000..c6506c072b --- /dev/null +++ b/src/test/java/nextstep/courses/repository/SessionRepositoryTest.java @@ -0,0 +1,60 @@ +package nextstep.courses.repository; + +import nextstep.courses.domain.*; +import nextstep.courses.infrastructure.JdbcImageFileRepository; +import nextstep.courses.infrastructure.JdbcSessionRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +@Import({JdbcSessionRepository.class, JdbcImageFileRepository.class}) +public class SessionRepositoryTest { + + @Autowired + JdbcSessionRepository jdbcSessionRepository; + + @Autowired + JdbcImageFileRepository jdbcImageFileRepository; + + @Test + void save() { + ImageFile imageFile = new ImageFile(1024*1024); + SessionPeriod period = new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(7)); + SessionStatus sessionStatus = SessionStatus.RECRUITING; + EnrollmentRule enrollmentRule = new PaidEnrollmentRule(50000, 10); + Enrollments enrollments = new Enrollments(); + + Session session = new Session(imageFile, period, sessionStatus, enrollmentRule, enrollments); + + Long sessionId = jdbcSessionRepository.save(session); + + assertThat(sessionId).isNotNull(); + } + + @Test + void find() { + ImageFile imageFile = new ImageFile(1024 * 1024, "png", 300 , 200); + jdbcImageFileRepository.save(imageFile); + + SessionPeriod period = new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(7)); + SessionStatus sessionStatus = SessionStatus.RECRUITING; + EnrollmentRule enrollmentRule = new PaidEnrollmentRule(50000, 10); + Enrollments enrollments = new Enrollments(); + + Session session = new Session(imageFile, period, sessionStatus, enrollmentRule, enrollments); + + + Long sessionId = jdbcSessionRepository.save(session); + + Session found = jdbcSessionRepository.findById(sessionId); + + assertThat(found.getSessionStatus()).isEqualTo(session.getSessionStatus()); + assertThat(found.getPeriod()).isEqualTo(session.getPeriod()); + } +} diff --git a/src/test/java/nextstep/courses/service/SessionServiceTest.java b/src/test/java/nextstep/courses/service/SessionServiceTest.java new file mode 100644 index 0000000000..ec63540d82 --- /dev/null +++ b/src/test/java/nextstep/courses/service/SessionServiceTest.java @@ -0,0 +1,49 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.*; +import nextstep.courses.repository.EnrollmentRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +class SessionServiceTest { + + @Autowired + SessionService sessionService; + + @Autowired + EnrollmentRepository enrollmentRepository; + + @Test + void 강의를_개설하고_조회하기() { + ImageFile imageFile = new ImageFile(1024 * 1024, "png", 300, 200); + SessionPeriod period = new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(7)); + + + Long sessionId = sessionService.createSession(imageFile, period, SessionStatus.RECRUITING, new PaidEnrollmentRule(50000, 10)); + + Session found = sessionService.findSession(sessionId); + + assertThat(found.getSessionStatus()) + .isEqualTo(SessionStatus.RECRUITING.toString()); + assertThat(found.getPeriod()).isEqualTo(period); + } + + @Test + void 수강신청_성공() { + ImageFile imageFile = new ImageFile(1024 * 1024, "png", 300, 200); + SessionPeriod period = new SessionPeriod(LocalDateTime.now(), LocalDateTime.now().plusDays(7)); + Long sessionId = sessionService.createSession(imageFile, period, SessionStatus.RECRUITING, new PaidEnrollmentRule(50000, 10)); + + sessionService.enroll(sessionId, 10L, new Money(50000)); + + assertThat(enrollmentRepository.findBySessionId(sessionId)).hasSize(1); + } +} \ No newline at end of file