diff --git a/md/step4.md b/md/step4.md new file mode 100644 index 000000000..5e863ca82 --- /dev/null +++ b/md/step4.md @@ -0,0 +1,29 @@ +# πŸš€ 4단계 - μˆ˜κ°•μ‹ μ²­(μš”κ΅¬μ‚¬ν•­ λ³€κ²½) + +## 핡심 ν•™μŠ΅ λͺ©ν‘œ + +- DB ν…Œμ΄λΈ”μ΄ 변경될 λ•Œλ„ μŠ€νŠΈλž­κΈ€λŸ¬ νŒ¨ν„΄μ„ μ μš©ν•΄ 점진적인 λ¦¬νŒ©ν„°λ§μ„ μ—°μŠ΅ν•œλ‹€. + - [μŠ€νŠΈλž­κΈ€λŸ¬(κ΅μ‚΄μž) νŒ¨ν„΄ - λ§ˆν‹΄ 파울러](https://martinfowler.com/bliki/StranglerFigApplication.html) + - [μŠ€νŠΈλž­κΈ€λŸ¬ 무화과 νŒ¨ν„΄](https://docs.microsoft.com/ko-kr/azure/architecture/patterns/strangler-fig) + +
+ +## λ³€κ²½λœ κΈ°λŠ₯ μš”κ΅¬μ‚¬ν•­ +### **κ°•μ˜ μˆ˜κ°•μ‹ μ²­μ€ κ°•μ˜ μƒνƒœκ°€ λͺ¨μ§‘쀑일 λ•Œλ§Œ κ°€λŠ₯ν•˜λ‹€.** +- [X] κ°•μ˜κ°€ 진행 쀑인 μƒνƒœμ—μ„œλ„ μˆ˜κ°•μ‹ μ²­μ΄ κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€. + - κ°•μ˜ 진행 μƒνƒœ(쀀비쀑, 진행쀑, μ’…λ£Œ)와 λͺ¨μ§‘ μƒνƒœ(λΉ„λͺ¨μ§‘쀑, λͺ¨μ§‘쀑)둜 μƒνƒœ 값을 뢄리해야 ν•œλ‹€. + +### **κ°•μ˜λŠ” κ°•μ˜ 컀버 이미지 정보λ₯Ό 가진닀.** +- [X] κ°•μ˜λŠ” **ν•˜λ‚˜ 이상**의 컀버 이미지λ₯Ό κ°€μ§ˆ 수 μžˆλ‹€. + +### 강사가 μŠΉμΈν•˜μ§€ μ•Šμ•„λ„ μˆ˜κ°• μ‹ μ²­ν•˜λŠ” λͺ¨λ“  μ‚¬λžŒμ΄ μˆ˜κ°• κ°€λŠ₯ν•˜λ‹€. +- [ ] μš°μ•„ν•œν…Œν¬μ½”μŠ€(무료), μš°μ•„ν•œν…Œν¬μΊ ν”„ Pro(유료)와 같이 μ„ λ°œλœ μΈμ›λ§Œ μˆ˜κ°• κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€. + - [ ] κ°•μ‚¬λŠ” μˆ˜κ°•μ‹ μ²­ν•œ μ‚¬λžŒ 쀑 μ„ λ°œλœ 인원에 λŒ€ν•΄μ„œλ§Œ μˆ˜κ°• 승인이 κ°€λŠ₯ν•΄μ•Ό ν•œλ‹€. + - [ ] κ°•μ‚¬λŠ” μˆ˜κ°•μ‹ μ²­ν•œ μ‚¬λžŒ 쀑 μ„ λ°œλ˜μ§€ μ•Šμ€ μ‚¬λžŒμ€ μˆ˜κ°•μ„ μ·¨μ†Œν•  수 μžˆμ–΄μ•Ό ν•œλ‹€. + +
+ +## ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬μ‚¬ν•­ +- λ¦¬νŒ©ν„°λ§ν•  λ•Œ 컴파일 μ—λŸ¬μ™€ 기쑴의 λ‹¨μœ„ ν…ŒμŠ€νŠΈμ˜ μ‹€νŒ¨λ₯Ό μ΅œμ†Œν™”ν•˜λ©΄μ„œ 점진적인 λ¦¬νŒ©ν„°λ§μ΄ κ°€λŠ₯ν•˜λ„λ‘ ν•œλ‹€. +- DB ν…Œμ΄λΈ”μ— 데이터가 μ‘΄μž¬ν•œλ‹€λŠ” κ°€μ •ν•˜μ— λ¦¬νŒ©ν„°λ§ν•΄μ•Ό ν•œλ‹€. + - 즉, 기쑴에 μŒ“μΈ 데이터λ₯Ό μ œκ±°ν•˜μ§€ μ•Šμ€ μƒνƒœλ‘œ λ¦¬νŒ©ν„°λ§ ν•΄μ•Ό ν•œλ‹€. diff --git a/src/main/java/nextstep/courses/domain/CoverImages.java b/src/main/java/nextstep/courses/domain/CoverImages.java new file mode 100644 index 000000000..969834ad1 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/CoverImages.java @@ -0,0 +1,21 @@ +package nextstep.courses.domain; + +import nextstep.courses.exception.EmptyCoverImageException; + +import java.util.List; + +public class CoverImages { + + private List images; + + public CoverImages(List images) { + validateIsNull(images); + this.images = images; + } + + private void validateIsNull(List images) { + if (images == null || images.isEmpty()) { + throw new EmptyCoverImageException(); + } + } +} diff --git a/src/main/java/nextstep/courses/domain/Period.java b/src/main/java/nextstep/courses/domain/Period.java index f57b036fb..b5a4d9606 100644 --- a/src/main/java/nextstep/courses/domain/Period.java +++ b/src/main/java/nextstep/courses/domain/Period.java @@ -25,10 +25,6 @@ private void validatePeriod(LocalDate startDate, LocalDate endDate) { } } - public boolean isDateWithinRange(LocalDate date) { - return !date.isBefore(startDate) && !date.isAfter(endDate); - } - public LocalDate startDate() { return startDate; } diff --git a/src/main/java/nextstep/courses/domain/RecruitmentStatus.java b/src/main/java/nextstep/courses/domain/RecruitmentStatus.java new file mode 100644 index 000000000..c39f26c24 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/RecruitmentStatus.java @@ -0,0 +1,27 @@ +package nextstep.courses.domain; + +import nextstep.courses.exception.InvalidRecruitmentStatusException; + +import java.util.Arrays; + +public enum RecruitmentStatus { + NOT_RECRUITMENT, + RECRUITING; + + private static final RecruitmentStatus[] VALUES = values(); + + public static RecruitmentStatus findByName(String name) { + return Arrays.stream(VALUES) + .filter(status -> status.name().equalsIgnoreCase(name)) + .findFirst() + .orElseThrow(() -> new InvalidRecruitmentStatusException(name)); + } + + public RecruitmentStatus ofRecruiting() { + return RECRUITING; + } + + public boolean isRecruiting() { + return this == RECRUITING; + } +} diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java index 2a12b369b..69e826883 100644 --- a/src/main/java/nextstep/courses/domain/Session.java +++ b/src/main/java/nextstep/courses/domain/Session.java @@ -1,8 +1,6 @@ package nextstep.courses.domain; import nextstep.courses.exception.InvalidSessionException; -import nextstep.courses.exception.NotOpenSessionException; -import nextstep.courses.exception.OutOfSessionException; import nextstep.payments.domain.Payment; import java.time.LocalDate; @@ -13,39 +11,39 @@ public class Session extends BaseEntity { private final Long id; private final Long courseId; private final SessionType type; - private final CoverImage coverImage; + private final CoverImages coverImages; private final Period period; - private Status status; + private final Status status; private final Students students; private final PaidCondition paidCondition; - public static Session ofFree(Long id, Long courseId, CoverImage coverImage, LocalDate startDate, LocalDate endDate) { - return new Session(id, courseId, SessionType.FREE, coverImage, new Period(startDate, endDate), Status.NOT_OPEN, 0, 0L, LocalDateTime.now(), null); + public static Session ofFree(Long id, Long courseId, CoverImages coverImages, LocalDate startDate, LocalDate endDate) { + return new Session(id, courseId, SessionType.FREE, coverImages, new Period(startDate, endDate), new Status(LocalDate.now(), startDate, endDate), 0, 0L, LocalDateTime.now(), null); } - public static Session ofPaid(Long id, Long courseId, CoverImage coverImage, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee) { - return new Session(id, courseId, SessionType.PAID, coverImage, new Period(startDate, endDate), Status.NOT_OPEN, maxStudents, fee, LocalDateTime.now(), null); + public static Session ofPaid(Long id, Long courseId, CoverImages coverImages, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee) { + return new Session(id, courseId, SessionType.PAID, coverImages, new Period(startDate, endDate), new Status(LocalDate.now(), startDate, endDate), maxStudents, fee, LocalDateTime.now(), null); } - public static Session of(Long id, Long courseId, SessionType type, CoverImage coverImage, Status status, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { - return new Session(id, courseId, type, coverImage, new Period(startDate, endDate), status, maxStudents, fee, createdAt, updatedAt); + public static Session of(Long id, Long courseId, SessionType type, CoverImages coverImages, RecruitmentStatus recruitmentStatus, LocalDate startDate, LocalDate endDate, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { + return new Session(id, courseId, type, coverImages, new Period(startDate, endDate), new Status(LocalDate.now(), startDate, endDate, recruitmentStatus), maxStudents, fee, createdAt, updatedAt); } - private Session(Long id, Long courseId, SessionType type, CoverImage coverImage, Period period, Status status, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { + private Session(Long id, Long courseId, SessionType type, CoverImages coverImages, Period period, Status status, int maxStudents, Long fee, LocalDateTime createdAt, LocalDateTime updatedAt) { super(createdAt, updatedAt); - validateNotNull(id, coverImage, period); + validateNotNull(id, coverImages, period); this.id = id; this.courseId = courseId; this.type = type; - this.coverImage = coverImage; + this.coverImages = coverImages; this.period = period; this.status = status; this.students = new Students(); this.paidCondition = new PaidCondition(maxStudents, fee); } - private void validateNotNull(Long id, CoverImage coverImage, Period period) { - if (id == null || coverImage == null || period == null) { + private void validateNotNull(Long id, CoverImages coverImages, Period period) { + if (id == null || coverImages == null || period == null) { throw new InvalidSessionException(); } } @@ -59,20 +57,11 @@ public void register(Payment payment) { } protected void validateStatus() { - if (!status.isOpen()) { - throw new NotOpenSessionException(); - } - } - - public void openSession() { - if (!period.isDateWithinRange(LocalDate.now())) { - throw new OutOfSessionException(); - } - changeStatusOpen(); + this.status.validate(); } - private void changeStatusOpen() { - this.status = status.ofOpen(); + public void startRecruiting() { + this.status.startRecruiting(); } public Long id() { @@ -83,16 +72,16 @@ public Long courseId() { return courseId; } - public Long imageId() { - return coverImage.getId(); - } - public String type() { return type.name(); } - public String status() { - return status.name(); + public String sessionStatus() { + return status.sessionStatus(); + } + + public String recruitmentStatus() { + return status.recruitmentStatus(); } public LocalDate startDate() { @@ -115,8 +104,9 @@ public Long fee() { public String toString() { return "Session{" + "id=" + id + + ", courseId=" + courseId + ", type=" + type + - ", coverImage=" + coverImage + + ", coverImages=" + coverImages + ", period=" + period + ", status=" + status + ", students=" + students + diff --git a/src/main/java/nextstep/courses/domain/SessionStatus.java b/src/main/java/nextstep/courses/domain/SessionStatus.java new file mode 100644 index 000000000..8138217b0 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/SessionStatus.java @@ -0,0 +1,25 @@ +package nextstep.courses.domain; + +import java.time.LocalDate; + +public enum SessionStatus { + PREPARING, + IN_PROGRESS, + COMPLETED; + + public static SessionStatus of(LocalDate now, LocalDate startDate, LocalDate endDate) { + if (now.isBefore(startDate)) { + return PREPARING; + } + + if (now.isAfter(startDate) && now.isBefore(endDate)) { + return IN_PROGRESS; + } + + return COMPLETED; + } + + public boolean isInProgress() { + return this == IN_PROGRESS; + } +} diff --git a/src/main/java/nextstep/courses/domain/Status.java b/src/main/java/nextstep/courses/domain/Status.java index fec47173b..578e80a91 100644 --- a/src/main/java/nextstep/courses/domain/Status.java +++ b/src/main/java/nextstep/courses/domain/Status.java @@ -1,27 +1,42 @@ package nextstep.courses.domain; -import nextstep.courses.exception.InvalidSessionStatusException; +import nextstep.courses.exception.NotOpenSessionException; -import java.util.Arrays; +import java.time.LocalDate; -public enum Status { - NOT_OPEN, - OPEN, - CLOSED; +public class Status { + private SessionStatus sessionStatus; + private RecruitmentStatus recruitmentStatus; - public Status ofOpen() { - return OPEN; + public Status(LocalDate now, LocalDate startDate, LocalDate endDate) { + this(SessionStatus.of(now, startDate, endDate), RecruitmentStatus.NOT_RECRUITMENT); } - public boolean isOpen() { - return this == OPEN; + public Status(LocalDate now, LocalDate startDate, LocalDate endDate, RecruitmentStatus recruitmentStatus) { + this(SessionStatus.of(now, startDate, endDate), recruitmentStatus); } - public static Status findByName(String name) { - return Arrays.stream(values()) - .filter(status -> status.name().equalsIgnoreCase(name)) - .findFirst() - .orElseThrow(() -> new InvalidSessionStatusException(name)); + public Status(SessionStatus sessionStatus, RecruitmentStatus recruitmentStatus) { + this.sessionStatus = sessionStatus; + this.recruitmentStatus = recruitmentStatus; + } + + public void validate() { + if (!sessionStatus.isInProgress() && !recruitmentStatus.isRecruiting()) { + throw new NotOpenSessionException(); + } + } + + public void startRecruiting() { + this.recruitmentStatus = recruitmentStatus.ofRecruiting(); + } + + public String sessionStatus() { + return this.sessionStatus.name(); + } + + public String recruitmentStatus() { + return this.recruitmentStatus.name(); } } diff --git a/src/main/java/nextstep/courses/exception/EmptyCoverImageException.java b/src/main/java/nextstep/courses/exception/EmptyCoverImageException.java new file mode 100644 index 000000000..49d5a79b8 --- /dev/null +++ b/src/main/java/nextstep/courses/exception/EmptyCoverImageException.java @@ -0,0 +1,8 @@ +package nextstep.courses.exception; + +public class EmptyCoverImageException extends RuntimeException { + + public EmptyCoverImageException() { + super("κ°•μ˜λŠ” ν•˜λ‚˜ μ΄μƒμ˜ 컀버 이미지λ₯Ό κ°€μ Έμ•Ό ν•©λ‹ˆλ‹€."); + } +} diff --git a/src/main/java/nextstep/courses/exception/InvalidRecruitmentStatusException.java b/src/main/java/nextstep/courses/exception/InvalidRecruitmentStatusException.java new file mode 100644 index 000000000..10067cd5a --- /dev/null +++ b/src/main/java/nextstep/courses/exception/InvalidRecruitmentStatusException.java @@ -0,0 +1,8 @@ +package nextstep.courses.exception; + +public class InvalidRecruitmentStatusException extends RuntimeException { + + public InvalidRecruitmentStatusException(String code) { + super("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ°•μ˜ λͺ¨μ§‘ μƒνƒœμž…λ‹ˆλ‹€." + code); + } +} diff --git a/src/main/java/nextstep/courses/exception/InvalidSessionStatusException.java b/src/main/java/nextstep/courses/exception/InvalidSessionStatusException.java deleted file mode 100644 index 2411415f8..000000000 --- a/src/main/java/nextstep/courses/exception/InvalidSessionStatusException.java +++ /dev/null @@ -1,8 +0,0 @@ -package nextstep.courses.exception; - -public class InvalidSessionStatusException extends RuntimeException { - - public InvalidSessionStatusException(String code) { - super("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ°•μ˜ μƒνƒœμž…λ‹ˆλ‹€." + code); - } -} diff --git a/src/main/java/nextstep/courses/exception/NotOpenSessionException.java b/src/main/java/nextstep/courses/exception/NotOpenSessionException.java index acad19812..68ada8d5a 100644 --- a/src/main/java/nextstep/courses/exception/NotOpenSessionException.java +++ b/src/main/java/nextstep/courses/exception/NotOpenSessionException.java @@ -2,6 +2,6 @@ public class NotOpenSessionException extends RuntimeException { public NotOpenSessionException() { - super("λͺ¨μ§‘ 쀑인 κ°•μ˜κ°€ μ•„λ‹™λ‹ˆλ‹€."); + super("진행 μ€‘μ΄κ±°λ‚˜ λͺ¨μ§‘ 쀑인 κ°•μ˜κ°€ μ•„λ‹™λ‹ˆλ‹€."); } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java index 3210c0491..aaa84a932 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcCoverImageRepository.java @@ -8,6 +8,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.List; @Repository("coverImageRepository") public class JdbcCoverImageRepository implements CoverImageRepository { @@ -19,23 +20,33 @@ public JdbcCoverImageRepository(JdbcOperations jdbcTemplate) { } @Override - public int save(CoverImage image) { - String sql = "insert into cover_image (id, size, extension, width, height, created_at) values(?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, image.getId(), image.getSize(), image.getExtension(), image.getWidth(), image.getHeight(), image.getCreatedAt()); + public int save(CoverImage image, Long sessionId) { + String sql = "insert into cover_image (id, session_id, size, extension, width, height, created_at) values(?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, image.getId(), sessionId, image.getSize(), image.getExtension(), image.getWidth(), image.getHeight(), image.getCreatedAt()); } @Override public CoverImage findById(Long id) { - String sql = "select id, size, extension, width, height, created_at, updated_at from cover_image where id = ?"; + String sql = "select id, session_id, size, extension, width, height, created_at, updated_at from cover_image where id = ?"; + return jdbcTemplate.queryForObject(sql, rowMapper(), id); + } + + @Override + public List findAllBySessionId(Long id) { + String sql = "select id, session_id, size, extension, width, height, created_at, updated_at from cover_image where session_id = ?"; + return jdbcTemplate.query(sql, rowMapper(), id); + } + + private RowMapper rowMapper() { RowMapper rowMapper = (rs, rowNum) -> new CoverImage( rs.getLong(1), - rs.getLong(2), - rs.getString(3), - rs.getInt(4), + rs.getLong(3), + rs.getString(4), rs.getInt(5), - toLocalDateTime(rs.getTimestamp(6)), - toLocalDateTime(rs.getTimestamp(7))); - return jdbcTemplate.queryForObject(sql, rowMapper, id); + rs.getInt(6), + toLocalDateTime(rs.getTimestamp(7)), + toLocalDateTime(rs.getTimestamp(8))); + return rowMapper; } private LocalDateTime toLocalDateTime(Timestamp timestamp) { diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java index 755df2281..cd6bb4add 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -1,9 +1,9 @@ package nextstep.courses.infrastructure; -import nextstep.courses.domain.CoverImage; +import nextstep.courses.domain.CoverImages; +import nextstep.courses.domain.RecruitmentStatus; import nextstep.courses.domain.Session; import nextstep.courses.domain.SessionType; -import nextstep.courses.domain.Status; import nextstep.courses.repository.CoverImageRepository; import nextstep.courses.repository.SessionRepository; import org.springframework.jdbc.core.JdbcOperations; @@ -29,25 +29,25 @@ public JdbcSessionRepository(JdbcOperations jdbcTemplate) { @Override public int save(Session session) { - String sql = "insert into session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - return jdbcTemplate.update(sql, session.id(), session.courseId(), session.imageId(), session.type(), session.status(), session.startDate(), session.endDate(), session.maxStudents(), session.fee(), session.getCreatedAt()); + String sql = "insert into session (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, session.id(), session.courseId(), session.type(), session.recruitmentStatus(), session.startDate(), session.endDate(), session.maxStudents(), session.fee(), session.getCreatedAt()); } @Override public Session findById(Long id) { - String sql = "select id, course_id, type, image_id, status, start_date, end_date, max_students, fee, created_at, updated_at from session where id = ?"; + String sql = "select id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at, updated_at from session where id = ?"; RowMapper rowMapper = (rs, rowNum) -> Session.of( rs.getLong(1), rs.getLong(2), SessionType.findByCode(rs.getString(3)), - coverImage(rs.getLong(4)), - Status.findByName(rs.getString(5)), + coverImages(id), + RecruitmentStatus.findByName(rs.getString(4)), + toLocalDate(rs.getDate(5)), toLocalDate(rs.getDate(6)), - toLocalDate(rs.getDate(7)), - rs.getInt(8), - rs.getLong(9), - toLocalDateTime(rs.getTimestamp(10)), - toLocalDateTime(rs.getTimestamp(11))); + rs.getInt(7), + rs.getLong(8), + toLocalDateTime(rs.getTimestamp(9)), + toLocalDateTime(rs.getTimestamp(10))); return jdbcTemplate.queryForObject(sql, rowMapper, id); } @@ -66,7 +66,7 @@ private LocalDate toLocalDate(Date date) { return date.toLocalDate(); } - private CoverImage coverImage(Long id) { - return coverImageRepository.findById(id); + private CoverImages coverImages(Long id) { + return new CoverImages(coverImageRepository.findAllBySessionId(id)); } } diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java index 147c958cb..eecc3c403 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/JdbcStudentsRepository.java @@ -5,8 +5,12 @@ import nextstep.users.domain.NsUser; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import java.sql.PreparedStatement; +import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.Set; @@ -20,22 +24,40 @@ public JdbcStudentsRepository(JdbcOperations jdbcTemplate) { } @Override - public int save(Long id, Long sessionId) { + public Long save(Long id, Long sessionId) { String sql = "insert into students (session_id, user_id, created_at) values (?, ?, ?)"; - return jdbcTemplate.update(sql, sessionId, id, LocalDateTime.now()); + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"}); + ps.setLong(1, sessionId); + ps.setLong(2, id); + ps.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now())); + return ps; + }, keyHolder); + + return keyHolder.getKey().longValue(); } @Override public Students findBySessionId(Long id) { - String sql = "select ns.id, ns.user_id, ns.password, ns.name, ns.email from students st inner join ns_user ns where st.user_id = ns.id and st.session_id = ?"; + String sql = "select ns.id, ns.user_id, ns.password, ns.name, ns.email, ns.created_at, ns.updated_at from students st inner join ns_user ns where st.user_id = ns.id and st.session_id = ?"; RowMapper rowMapper = (rs, rowNum) -> new NsUser( rs.getLong(1), rs.getString(2), rs.getString(3), rs.getString(4), - rs.getString(5) + rs.getString(5), + toLocalDateTime(rs.getTimestamp(6)), + toLocalDateTime(rs.getTimestamp(7)) ); return new Students(Set.of(jdbcTemplate.queryForObject(sql, rowMapper, id))); } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } } diff --git a/src/main/java/nextstep/courses/repository/CoverImageRepository.java b/src/main/java/nextstep/courses/repository/CoverImageRepository.java index 44948543c..7473f5c99 100644 --- a/src/main/java/nextstep/courses/repository/CoverImageRepository.java +++ b/src/main/java/nextstep/courses/repository/CoverImageRepository.java @@ -2,8 +2,12 @@ import nextstep.courses.domain.CoverImage; +import java.util.List; + public interface CoverImageRepository { - int save(CoverImage coverImage); + int save(CoverImage coverImage, Long sessionId); CoverImage findById(Long id); + + List findAllBySessionId(Long id); } diff --git a/src/main/java/nextstep/courses/repository/StudentsRepository.java b/src/main/java/nextstep/courses/repository/StudentsRepository.java index c05268630..77d5edd63 100644 --- a/src/main/java/nextstep/courses/repository/StudentsRepository.java +++ b/src/main/java/nextstep/courses/repository/StudentsRepository.java @@ -4,7 +4,7 @@ public interface StudentsRepository { - int save(Long id, Long sessionId); + Long save(Long id, Long sessionId); Students findBySessionId(Long id); } diff --git a/src/main/java/nextstep/courses/service/CoverImageService.java b/src/main/java/nextstep/courses/service/CoverImageService.java new file mode 100644 index 000000000..31488b613 --- /dev/null +++ b/src/main/java/nextstep/courses/service/CoverImageService.java @@ -0,0 +1,18 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.CoverImage; +import nextstep.courses.repository.CoverImageRepository; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; + +public class CoverImageService { + + @Resource(name = "coverImageRepository") + private CoverImageRepository coverImageRepository; + + @Transactional + public void createCoverImage(CoverImage image, Long sessionId) { + coverImageRepository.save(image, sessionId); + } +} diff --git a/src/main/java/nextstep/users/domain/NsUser.java b/src/main/java/nextstep/users/domain/NsUser.java index 25050fee3..91c4d8289 100755 --- a/src/main/java/nextstep/users/domain/NsUser.java +++ b/src/main/java/nextstep/users/domain/NsUser.java @@ -124,14 +124,13 @@ public boolean isGuestUser() { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; NsUser nsUser = (NsUser) o; - return Objects.equals(id, nsUser.id) && Objects.equals(userId, nsUser.userId) && Objects.equals(password, nsUser.password) && Objects.equals(name, nsUser.name) && Objects.equals(email, nsUser.email); + return id != null & Objects.equals(id, nsUser.id); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), id, userId, password, name, email); + return id != null ? id.intValue() : 0; } @Override diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 0d92f4161..118092d41 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -11,8 +11,9 @@ INSERT INTO question (id, writer_id, title, contents, created_at, deleted) VALUE INSERT INTO course (id, title, creator_id, created_at) VALUES (2, 'JPA ν™œμš©νŽΈ1', 2, CURRENT_TIMESTAMP()); -INSERT INTO cover_image (id, size, extension, width, height, created_at) VALUES (2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); - -INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 2, 'FREE', 'NOT_OPEN', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); -INSERT INTO session (id, course_id, image_id, type, status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 2, 'PAID', 'NOT_OPEN', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); +INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (2, 3, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); +INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (3, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); +INSERT INTO cover_image (id, session_id, size, extension, width, height, created_at) VALUES (4, 2, 1, 'jpg', 300, 200, CURRENT_TIMESTAMP()); +INSERT INTO session (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (2, 2, 'FREE', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 0, 0, CURRENT_TIMESTAMP()); +INSERT INTO session (id, course_id, type, recruitment_status, start_date, end_date, max_students, fee, created_at) VALUES (3, 2, 'PAID', 'NOT_RECRUITMENT', '2023-12-01', '2023-12-31', 30, 20000, CURRENT_TIMESTAMP()); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 4e0d8e4fb..0b9580c0d 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -51,6 +51,7 @@ create table delete_history ( create table cover_image ( id bigint not null, + session_id bigint not null, size double not null, extension varchar(10) not null, width int not null, @@ -63,9 +64,8 @@ create table cover_image ( create table session ( id bigint not null, course_id bigint not null, - image_id bigint not null, type varchar(10) not null, - status varchar(10) not null, + recruitment_status varchar(30) not null, start_date timestamp not null, end_date timestamp not null, max_students int, diff --git a/src/test/java/nextstep/courses/domain/CourseTest.java b/src/test/java/nextstep/courses/domain/CourseTest.java index 24460c689..d6f5c34ed 100644 --- a/src/test/java/nextstep/courses/domain/CourseTest.java +++ b/src/test/java/nextstep/courses/domain/CourseTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import java.time.LocalDate; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -15,7 +16,7 @@ void add_session() { Course course = new Course(0L, "TDD, 클린 μ½”λ“œ with Java 17κΈ°", 1L); CoverImage coverImage = new CoverImage(1, "jpg", 300, 200); LocalDate now = LocalDate.now(); - Session session = Session.ofFree(0L, 1L, coverImage, now, now); + Session session = Session.ofFree(0L, 1L, new CoverImages(List.of(coverImage)), now, now); course.addSession(session); diff --git a/src/test/java/nextstep/courses/domain/SessionStatusTest.java b/src/test/java/nextstep/courses/domain/SessionStatusTest.java new file mode 100644 index 000000000..f7909fb68 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/SessionStatusTest.java @@ -0,0 +1,24 @@ +package nextstep.courses.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; + +class SessionStatusTest { + + @Test + @DisplayName("였늘 λ‚ μ§œμ— λ§žλŠ” 쀀비쀑, 진행쀑, μ’…λ£Œλ₯Ό λ°˜ν™˜ν•œλ‹€.") + void create() { + SessionStatus preparing = SessionStatus.of(LocalDate.now(), LocalDate.of(2023, 12, 15), LocalDate.of(2023, 12, 31)); + assertThat(preparing).isEqualTo(SessionStatus.PREPARING); + + SessionStatus inProgress = SessionStatus.of(LocalDate.now(), LocalDate.of(2023, 12, 1), LocalDate.of(2023, 12, 31)); + assertThat(inProgress).isEqualTo(SessionStatus.IN_PROGRESS); + + SessionStatus completed = SessionStatus.of(LocalDate.now(), LocalDate.of(2023, 12, 1), LocalDate.of(2023, 12, 5)); + assertThat(completed).isEqualTo(SessionStatus.COMPLETED); + } +} diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java index f2b8fa35c..75a7637d3 100644 --- a/src/test/java/nextstep/courses/domain/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/SessionTest.java @@ -7,35 +7,36 @@ import org.junit.jupiter.api.Test; import java.time.LocalDate; +import java.util.List; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; class SessionTest { private static final LocalDate START_DATE = LocalDate.of(2023, 12, 1); private static final LocalDate END_DATE = LocalDate.of(2023, 12, 31); - @Test - @DisplayName("무료 κ°•μ˜ μˆ˜κ°• μ‹ μ²­ μ‹œ κ°•μ˜ μƒνƒœκ°€ λͺ¨μ§‘쀑이 μ•„λ‹ˆλ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") - void register_status_check() { - Session freeSession = Session.ofFree(1L, 2L, coverImage(), START_DATE, END_DATE); - assertThatThrownBy(() -> freeSession.register(Payment.ofFree(1L, NsUserTest.JAVAJIGI))) - .isInstanceOf(NotOpenSessionException.class); - } - - @Test - @DisplayName("κ°•μ˜ μƒνƒœλ₯Ό λͺ¨μ§‘μ€‘μœΌλ‘œ λ³€κ²½ μ‹œ ν˜„μž¬ λ‚ μ§œκ°€ κ°•μ˜ 기간에 μ†ν•˜μ§€ μ•ŠμœΌλ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") - void register_open() { - Session session = Session.ofFree(1L, 2L, coverImage(), START_DATE, LocalDate.of(2023, 12, 2)); - assertThatThrownBy(() -> session.openSession()) - .isInstanceOf(OutOfSessionException.class); - } +// @Test +// @DisplayName("무료 κ°•μ˜ μˆ˜κ°• μ‹ μ²­ μ‹œ κ°•μ˜ μƒνƒœκ°€ λͺ¨μ§‘쀑이 μ•„λ‹ˆλ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") +// void register_status_check() { +// Session freeSession = Session.ofFree(1L, 2L, coverImages(), START_DATE, END_DATE); +// assertThatThrownBy(() -> freeSession.register(Payment.ofFree(1L, NsUserTest.JAVAJIGI))) +// .isInstanceOf(NotOpenSessionException.class); +// } + +// @Test +// @DisplayName("κ°•μ˜ μƒνƒœλ₯Ό λͺ¨μ§‘μ€‘μœΌλ‘œ λ³€κ²½ μ‹œ ν˜„μž¬ λ‚ μ§œκ°€ κ°•μ˜ 기간에 μ†ν•˜μ§€ μ•ŠμœΌλ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") +// void register_open() { +// Session session = Session.ofFree(1L, 2L, coverImages(), START_DATE, LocalDate.of(2023, 12, 2)); +// assertThatThrownBy(() -> session.startRecruiting()) +// .isInstanceOf(OutOfSessionException.class); +// } @Test @DisplayName("유료 κ°•μ˜ μˆ˜κ°• μ‹ μ²­ μ‹œ μ΅œλŒ€ μˆ˜κ°• 인원을 μ΄ˆκ³Όν•˜λ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") void register_over_students() { - Session paidSession = Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, 1, 10_000L); - paidSession.openSession(); + Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); + paidSession.startRecruiting(); paidSession.register(Payment.ofPaid(1L, 1L, NsUserTest.JAVAJIGI, 10_000L)); @@ -46,8 +47,8 @@ void register_over_students() { @Test @DisplayName("유료 κ°•μ˜ μˆ˜κ°• μ‹ μ²­ μ‹œ κ²°μ œκΈˆμ•‘κ³Ό μˆ˜κ°•λ£Œκ°€ μΌμΉ˜ν•˜λŠ”μ§€ ν™•μΈν•˜μ§€ μ•ŠμœΌλ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") void session_fee_test() { - Session paidSession = Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, 1, 10_000L); - paidSession.openSession(); + Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); + paidSession.startRecruiting(); assertThatThrownBy(() -> paidSession.register(Payment.ofPaid(2L, 1L, NsUserTest.SANJIGI, 8_000L))) .isInstanceOf(PaymentMismatchException.class); @@ -56,15 +57,15 @@ void session_fee_test() { @Test @DisplayName("κ°•μ˜ 생성 μ‹œ κ²°μ œκΈˆμ•‘κ³Ό μˆ˜κ°•λ£Œκ°€ 음수면 μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") void session_paid_condition_null() { - assertThatThrownBy(() -> Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, -1, -1L)) + assertThatThrownBy(() -> Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, -1, -1L)) .isInstanceOf(NegativePaidConditionException.class); } @Test @DisplayName("쀑볡 μˆ˜κ°• μ‹ μ²­ μ‹œ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") void duplicate_register() { - Session paidSession = Session.ofPaid(1L, 2L, coverImage(), START_DATE, END_DATE, 2, 10_000L); - paidSession.openSession(); + Session paidSession = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 2, 10_000L); + paidSession.startRecruiting(); paidSession.register(Payment.ofPaid(1L, 1L, NsUserTest.SANJIGI, 10_000L)); @@ -72,7 +73,30 @@ void duplicate_register() { .isInstanceOf(DuplicateStudentsException.class); } - private static CoverImage coverImage() { - return new CoverImage(1, "gif", 300, 200); + @Test + @DisplayName("κ°•μ˜ 생성 μ‹œ 이미지가 μ—†μœΌλ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.") + void empty_images() { + assertThatThrownBy(() -> Session.ofPaid(1L, 2L, new CoverImages(null), START_DATE, END_DATE, 1, 10_000L)) + .isInstanceOf(EmptyCoverImageException.class); + } + + @Test + @DisplayName("κ°•μ˜ 생성 μ‹œ κΈ°κ°„ λ‚΄λ©΄ μƒνƒœκ°€ 쀀비쀑이고 λͺ¨μ§‘ 전이닀.") + void session_status() { + Session session = Session.ofPaid(1L, 2L, coverImages(), START_DATE, END_DATE, 1, 10_000L); + assertThat(session.sessionStatus()).isEqualTo(SessionStatus.IN_PROGRESS.name()); + assertThat(session.recruitmentStatus()).isEqualTo(RecruitmentStatus.NOT_RECRUITMENT.name()); + } + + @Test + @DisplayName("κ°•μ˜ μƒνƒœκ°€ λͺ¨μ§‘쀑이 μ•„λ‹ˆμ–΄λ„ 진행 쀑이면 μˆ˜κ°•μ‹ μ²­μ΄ κ°€λŠ₯ν•˜λ‹€.") + void can_register_not_recruitment() { + Session freeSession = Session.ofFree(1L, 2L, coverImages(), START_DATE, END_DATE); + assertThatCode(() -> freeSession.register(Payment.ofFree(1L, NsUserTest.JAVAJIGI))) + .doesNotThrowAnyException(); + } + + private static CoverImages coverImages() { + return new CoverImages(List.of(new CoverImage(1, "gif", 300, 200))); } } diff --git a/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java index c3d3e8cac..4589f3f51 100644 --- a/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CoverImageRepositoryTest.java @@ -10,6 +10,8 @@ import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.jdbc.core.JdbcTemplate; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; @JdbcTest @@ -30,7 +32,7 @@ void setUp() { @Test void crud() { CoverImage image = new CoverImage(1, "jpg", 300, 200); - int count = coverImageRepository.save(image); + int count = coverImageRepository.save(image, 2L); assertThat(count).isEqualTo(1); CoverImage savedImage = coverImageRepository.findById(1L); assertThat(savedImage.getId()).isEqualTo(image.getId()); @@ -41,4 +43,11 @@ void crud() { void find() { assertThat(coverImageRepository.findById(2L).getId()).isEqualTo(2L); } + + @Test + void findAllBySessionId() { + List images = coverImageRepository.findAllBySessionId(2L); + assertThat(images.get(0).getId()).isEqualTo(3L); + assertThat(images.get(1).getId()).isEqualTo(4L); + } } diff --git a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java index 9372e44ad..18fd14828 100644 --- a/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/SessionRepositoryTest.java @@ -1,8 +1,8 @@ package nextstep.courses.infrastructure; import nextstep.courses.domain.CoverImage; +import nextstep.courses.domain.CoverImages; import nextstep.courses.domain.Session; -import nextstep.courses.repository.CoverImageRepository; import nextstep.courses.repository.SessionRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,6 +13,7 @@ import org.springframework.jdbc.core.JdbcOperations; import java.time.LocalDate; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -26,17 +27,15 @@ class SessionRepositoryTest { private JdbcOperations jdbcTemplates; private SessionRepository sessionRepository; - private CoverImageRepository coverImageRepository; @BeforeEach void setUp() { sessionRepository = new JdbcSessionRepository(jdbcTemplates); - coverImageRepository = new JdbcCoverImageRepository(jdbcTemplates); } @Test void free_crud() { - Session session = Session.ofFree(1L, 2L, coverImage(), LocalDate.now(), LocalDate.of(2023, 12, 31)); + Session session = Session.ofFree(1L, 2L, coverImages(), LocalDate.now(), LocalDate.of(2023, 12, 31)); int count = sessionRepository.save(session); assertThat(count).isEqualTo(1); @@ -47,7 +46,7 @@ void free_crud() { @Test void paid_crud() { - Session session = Session.ofPaid(1L, 2L, coverImage(), LocalDate.now(), LocalDate.of(2023, 12, 31), 10, 10_000L); + Session session = Session.ofPaid(1L, 2L, coverImages(), LocalDate.now(), LocalDate.of(2023, 12, 31), 10, 10_000L); int count = sessionRepository.save(session); assertThat(count).isEqualTo(1); @@ -60,7 +59,7 @@ void paid_crud() { LOGGER.debug("Session: {}", paidSession); } - private CoverImage coverImage() { - return coverImageRepository.findById(2L); + private CoverImages coverImages() { + return new CoverImages(List.of(new CoverImage(1, "jpg", 300, 200))); } } diff --git a/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java index ccd17d206..db57a6084 100644 --- a/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/StudentsRepositoryTest.java @@ -37,15 +37,14 @@ void setUp() { void crud() { NsUser nsUser = new NsUser(1L, "javajigi", "test", "μžλ°”μ§€κΈ°", "javajigi@slipp.net"); Session session = sessionRepository.findById(3L); - session.openSession(); + session.startRecruiting(); - session.register(Payment.ofPaid(1L, 1L, nsUser, 20_000L)); - int count = studentsRepository.save(nsUser.getId(), 2L); - assertThat(count).isEqualTo(1); + session.register(Payment.ofPaid(1L, 3L, nsUser, 20_000L)); + Long id = studentsRepository.save(nsUser.getId(), 3L); + assertThat(id).isEqualTo(1L); - Students students = studentsRepository.findBySessionId(2L); - System.out.println(students); - assertThat(students.isContains(nsUser)).isTrue(); //μ™œ false인지 λͺ¨λ₯΄κ² μŒ γ… γ…  + Students students = studentsRepository.findBySessionId(3L); + assertThat(students.isContains(nsUser)).isTrue(); LOGGER.debug("Students: {}", students); } }