Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step2 #353

Open
wants to merge 24 commits into
base: suzhanlee
Choose a base branch
from
Open

Step2 #353

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
588f420
feat : LMS step 2 전체적인 골격 생성
suzhanlee Dec 3, 2023
007b730
refactor : DeleteHistory answer 정적 팩토리 메소드로 변수 줄이기
suzhanlee Dec 4, 2023
427c601
refactor : DeleteHistory question 정적 팩토리 메소드로 변수 줄이기
suzhanlee Dec 4, 2023
961a0e1
refactor : 사용하지 않는 getter, setter 제거
suzhanlee Dec 4, 2023
2c93348
Docs : LMS step2 기능 목록 보완
suzhanlee Dec 4, 2023
6f12941
feat : ImageSize 이미지의 width는 300픽셀 이상인지 검증하는 기능 추가
suzhanlee Dec 4, 2023
c0a7182
feat : ImageSize 이미지의 height는 200픽셀 이상인지 검증하는 기능 추가
suzhanlee Dec 5, 2023
53c9333
feat : ImageSize width와 height의 비율은 3:2 인지 검증하는 기능 추가
suzhanlee Dec 5, 2023
cdad875
feat : ImageType 사용할 수 있는 이미지 타입인지 검증하는 기능 추가
suzhanlee Dec 5, 2023
d70eb14
feat : ImageCapacity 이미지 용량이 1MB 이하인지 검증하는 기능 추가
suzhanlee Dec 7, 2023
9350996
feat : SessionStatus 강의를 수강할 수 있는지 알려주는 기능 추가
suzhanlee Dec 7, 2023
4a989c7
feat : SessionParticipants 강의에 사람이 다 찼는지 확인하는 기능 추가
suzhanlee Dec 7, 2023
6b93913
feat : Session 강의 타입에 따라 수강생을 등록하는 기능 추가
suzhanlee Dec 8, 2023
680e35e
feat : SessionPeriod 강의가 시작했는지 확인하는 기능 추가
suzhanlee Dec 8, 2023
6af0cd9
feat : CourseService 저장 및 조회 기능 추가
suzhanlee Dec 8, 2023
ee0b030
feat : ImageService 저장 및 조회 기능 추가
suzhanlee Dec 8, 2023
bb9310a
refactor : Enrollment 에게 Session의 강의 등록 책임 위임
suzhanlee Dec 8, 2023
01c19ee
feat : SessionService 강의를 저장하고, 조회하는 기능 추가
suzhanlee Dec 8, 2023
2ad8e47
feat : SessionService 수강 신청 기능 추가
suzhanlee Dec 8, 2023
6df635f
test : SessionServiceTest 강의 수강 신청 테스트
suzhanlee Dec 8, 2023
0998cc6
test : SessionServiceTest 강좌를 저장하는 기능 테스트
suzhanlee Dec 8, 2023
9fda807
Style : 코드 서식 재정리 및 import 문 최적화
suzhanlee Dec 8, 2023
707f33d
remove : 필요없는 클래스 삭제
suzhanlee Dec 8, 2023
7ace3b4
fix : 테스트 오류나는 부분 코드 수정
suzhanlee Dec 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 76 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,94 @@
# 학습 관리 시스템(Learning Management System)

## 진행 방법

* 학습 관리 시스템의 수강신청 요구사항을 파악한다.
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 온라인 코드 리뷰 과정

* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)

## LMS STEP 1 기능 목록

## LMS 기능 목록
* `Question`
* [x] : 로그인 사용자, 질문자와 모든 답변자들이 같으면 답변을 삭제 상태로 바꿀 수 있다.
* [x] : 질문이 삭제되면 삭제 히스토리에게 질문 삭제 요청을 보낸다.
* [x] : 질문이 삭제되면 삭제 히스토리에게 질문에 대한 댓글들의 삭제 요청을 보낸다.
* [x] : 질문이 삭제되면 삭제 히스토리에게 질문 삭제 요청을 보낸다.
* [x] : 질문이 삭제되면 삭제 히스토리에게 질문에 대한 댓글들의 삭제 요청을 보낸다.
* [x] : 삭제할 수 없다면 예외를 던진다.
* [x] : 사용자와 질문자가 달라 삭제할 수 없다면 예외를 던진다.
* [x] : 질문자와 답변자들이 달라 삭제할 수 없다면 예외를 던진다.
* [x] : 사용자와 질문자가 달라 삭제할 수 없다면 예외를 던진다.
* [x] : 질문자와 답변자들이 달라 삭제할 수 없다면 예외를 던진다.

* `Answer`
* [x] : 질문자와 답변자가 같은지 다른지 확인한다.
* [x] : 질문이 삭제되면, 댓글도 삭제한다.

## 수강 신청 기능 요구사항

* 과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다.
* 강의는 시작일과 종료일을 가진다.
* 강의는 강의 커버 이미지 정보를 가진다.
* 이미지 크기는 1MB 이하여야 한다.
* 이미지 타입은 gif, jpg(jpeg 포함),, png, svg만 허용한다.
* 이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다.
* 강의는 무료 강의와 유료 강의로 나뉜다.
* 무료 강의는 최대 수강 인원 제한이 없다.
* 유료 강의는 강의 최대 수강 인원을 초과할 수 없다.
* 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능하다.
* 강의 상태는 준비중, 모집중, 종료 3가지 상태를 가진다.
* 강의 수강신청은 강의 상태가 모집중일 때만 가능하다.
* 유료 강의의 경우 결제는 이미 완료한 것으로 가정하고 이후 과정을 구현한다.
* 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반한된다.

## LMS STEP 2 기능 목록

* `Course` : 과정
* [] :

* `Session` : 강의
* [x] : 강의 타입에 따라 수강생을 등록한다.

* `SessionParticipants` : 강의 수강 인원 정보
* [x] : 강의에 사람이 다 찼는지 확인한다.

* `SessionStatus` : 강의 상태
* [x] : 강의를 수강할 수 있는지 알려준다.

* `SessionType` : 강의 타입
* [x] : 강의 타입이 유료인지 무료인지 확인한다.

* `SessionPeriod` : 강의 기간
* [x] : 강의가 시작했는지 확인한다.

* `Image` : 이미지
* [] :

* `ImageSize` : 이미지 사이즈
* [x] : 이미지의 width는 300픽셀 이상인지 검증한다.
* [x] : 이미지의 height는 200픽셀 이상인지 검증한다.
* [x] : width와 height의 비율은 3:2 인지 검증한다.

* `ImageType` : 이미지 타입
* [x] : 사용할 수 있는 이미지 타입인지 검증한다.

* `ImageCapacityType` : 이미지 용량 타입
* [x] : 이미지 용량 이름으로 이미지 용량 타입을 찾는다.

* `ImageCapacity` : 이미지 용량
* [x] : 이미지 용량이 1MB 이하인지 검증한다.

* `Enrollment` : 등록
* [x] : 유료 강의 수강 신청을 한다.
* [x] : 무료 강의 수강 신청을 한다.
* [x] : 유료 강의의 경우, 수강 인원이 모두 채워지면 예외를 던진다.

* `Payments` : 결제 정보들
* [] :

* `SessionPayment` : 결제 정보
* [] :



40 changes: 40 additions & 0 deletions src/main/java/nextstep/courses/domain/Course.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package nextstep.courses.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import nextstep.session.domain.Session;

public class Course {
private Long id;
Expand All @@ -13,13 +17,19 @@ public class Course {

private LocalDateTime updatedAt;

private List<Session> sessions = new ArrayList<>();

Comment on lines +20 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private List<Session> sessions = new ArrayList<>();
private List<Session> sessions;
private Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt, List<Session> sessions) {
this.id = id;
this.title = title;
this.creatorId = creatorId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.sessions = sessions;
}
public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.title = title;
this.creatorId = creatorId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.sessions = new ArrayList<>();
}

저는 생성자를 다양하게 사용하기 위해
초기화는 생성자에서 해줄것 같아요.

생성자가 다양하면, 테스트 할때
assSession 같은 메소드를 사용하지 않고도
바로 Course를 생성할 수 있는게 장점인것 같아요.

public Course() {
}

public Course(String title, Long creatorId) {
this(0L, title, creatorId, LocalDateTime.now(), null);
}

public Course(Long id, String title, Long creatorId) {
this(id, title, creatorId, LocalDateTime.now(), null);
}

public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.title = title;
Expand All @@ -28,6 +38,14 @@ public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, Lo
this.updatedAt = updatedAt;
}

public void addSession(Session session) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SessionService의 save하는 부분에 course에 연관관계를 맺어주기 위해 addSession을 했는데, sevice에서 add를 해도 될까요?

과정(Course)은 기수 단위로 운영하며, 여러 개의 강의(Session)를 가질 수 있다.
과정이 여러개의 강의를 가질수 있으므로, 과정 -> 강의를 관리한다라는 관점으로 저도 보고있으므로
잘 구현해주셨습니다!

sessions.add(session);
}

public Long getId() {
return id;
}

public String getTitle() {
return title;
}
Expand All @@ -40,6 +58,28 @@ public LocalDateTime getCreatedAt() {
return createdAt;
}

public List<Session> getSessions() {
return sessions;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Course course = (Course) o;
return Objects.equals(title, course.title) && Objects.equals(creatorId, course.creatorId)
&& Objects.equals(sessions, course.sessions);
}

@Override
public int hashCode() {
return Objects.hash(title, creatorId, sessions);
}

@Override
public String toString() {
return "Course{" +
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/nextstep/courses/dto/CreateCourseRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package nextstep.courses.dto;

import nextstep.courses.domain.Course;

public class CreateCourseRequest {

private String title;
private Long creatorId;

public Course toEntity() {
return new Course(title, creatorId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package nextstep.courses.infrastructure;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import nextstep.courses.domain.Course;
import nextstep.courses.domain.CourseRepository;

public class MockCourseRepository implements CourseRepository {

private Map<Integer, Course> courseStorage = new LinkedHashMap<>();

public MockCourseRepository() {
this.courseStorage.put(1, new Course(1L, "코스", 1L));
}

@Override
public int save(Course course) {
long courseId = Optional.ofNullable(course.getId())
.orElseGet(() -> (long) (courseStorage.size() + 1));
int intCourseId = Math.toIntExact(courseId);
courseStorage.put(intCourseId, course);
return intCourseId;
}

@Override
public Course findById(Long id) {
return Optional.ofNullable(courseStorage.get(Integer.parseInt(String.valueOf(id))))
.orElseThrow(() -> new IllegalStateException("과정을 찾을 수 없습니다."));
}
}
26 changes: 26 additions & 0 deletions src/main/java/nextstep/courses/sevice/CourseService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.courses.sevice;

import nextstep.courses.domain.Course;
import nextstep.courses.domain.CourseRepository;
import nextstep.courses.dto.CreateCourseRequest;

public class CourseService {

private final CourseRepository courseRepository;

public CourseService(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}

public int saveCourse(CreateCourseRequest request) {
return courseRepository.save(request.toEntity());
}

public void saveCourse(Course course) {
courseRepository.save(course);
}

public Course findCourse(Long courseId) {
return courseRepository.findById(courseId);
}
}
36 changes: 36 additions & 0 deletions src/main/java/nextstep/image/domain/Image.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package nextstep.image.domain;

import java.util.Objects;

public class Image {

private ImageCapacity imageCapacity;

private ImageType imageType;

private ImageSize imageSize;

public Image(ImageCapacity imageCapacity, ImageType imageType, ImageSize imageSize) {
this.imageCapacity = imageCapacity;
this.imageType = imageType;
this.imageSize = imageSize;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Image image = (Image) o;
return Objects.equals(imageCapacity, image.imageCapacity) && imageType == image.imageType
&& Objects.equals(imageSize, image.imageSize);
}

@Override
public int hashCode() {
return Objects.hash(imageCapacity, imageType, imageSize);
}
}
62 changes: 62 additions & 0 deletions src/main/java/nextstep/image/domain/ImageCapacity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package nextstep.image.domain;

import java.util.Objects;
import nextstep.image.exception.OutOfRangeCapacityException;
import nextstep.image.exception.OutOfRangeCapacityTypeException;

public class ImageCapacity {

public static final String OUT_OF_RANGE_CAPACITY_EXCEPTION = "이미지 용량이 1MB를 넘습니다.";
public static final String OUT_OF_RANGE_CAPACITY_TYPE_EXCEPTION = "이미지 용량 타입이 MB를 넘습니다.";

private long value;
private ImageCapacityType imageCapacityType;

public ImageCapacity(long value, ImageCapacityType imageCapacityType) {
validateImageCapacity(value, imageCapacityType);
this.value = value;
this.imageCapacityType = imageCapacityType;
}

public ImageCapacity(long value, String capacityTypeName) {
this(value, ImageCapacityType.findByName(capacityTypeName));
}

private static void validateImageCapacity(long value, ImageCapacityType imageCapacityType) {
validateCapacityTypeRange(imageCapacityType);
validateCapacityRange(value, imageCapacityType);
}

private static void validateCapacityTypeRange(ImageCapacityType imageCapacityType) {
if (imageCapacityType.isGreaterThanMB()) {
throw new OutOfRangeCapacityTypeException(OUT_OF_RANGE_CAPACITY_TYPE_EXCEPTION);
}
}

private static void validateCapacityRange(long value, ImageCapacityType imageCapacityType) {
if (outOfRange(value, imageCapacityType)) {
throw new OutOfRangeCapacityException(OUT_OF_RANGE_CAPACITY_EXCEPTION);
}
}

private static boolean outOfRange(long value, ImageCapacityType imageCapacityType) {
return imageCapacityType.isLessThanMB() && value > 1;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ImageCapacity that = (ImageCapacity) o;
return value == that.value && imageCapacityType == that.imageCapacityType;
}

@Override
public int hashCode() {
return Objects.hash(value, imageCapacityType);
}
}
34 changes: 34 additions & 0 deletions src/main/java/nextstep/image/domain/ImageCapacityType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package nextstep.image.domain;

import java.util.Arrays;
import nextstep.image.exception.CannotFindImageCapacityTypeException;

public enum ImageCapacityType {

KB("kb"),
MB("mb"),
GB("gb");

public static final String CANNOT_FIND_IMAGE_CAPACITY_TYPE_EXCEPTION = "이미지 용량 타입을 찾을 수 없습니다.";

private final String name;

ImageCapacityType(String name) {
this.name = name;
}

public static ImageCapacityType findByName(String name) {
return Arrays.stream(values())
.filter(imageCapacityType -> imageCapacityType.name.equals(name))
.findFirst()
.orElseThrow(() -> new CannotFindImageCapacityTypeException(CANNOT_FIND_IMAGE_CAPACITY_TYPE_EXCEPTION));
}

public boolean isLessThanMB() {
return !this.equals(KB);
}

public boolean isGreaterThanMB() {
return this.equals(GB);
}
}
Loading