diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/course/implement/CourseRepository.kt b/src/main/kotlin/com/yourssu/soongpt/domain/course/implement/CourseRepository.kt index 00388e7..9aaf791 100644 --- a/src/main/kotlin/com/yourssu/soongpt/domain/course/implement/CourseRepository.kt +++ b/src/main/kotlin/com/yourssu/soongpt/domain/course/implement/CourseRepository.kt @@ -3,4 +3,5 @@ package com.yourssu.soongpt.domain.course.implement interface CourseRepository { fun save(course: Course): Course fun findAllByDepartmentId(departmentId: Long, classification: Classification): List + fun getAll(ids: List): List } diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/course/implement/CourseWriter.kt b/src/main/kotlin/com/yourssu/soongpt/domain/course/implement/CourseWriter.kt new file mode 100644 index 0000000..164c9d6 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/course/implement/CourseWriter.kt @@ -0,0 +1,12 @@ +package com.yourssu.soongpt.domain.course.implement + +import org.springframework.stereotype.Component + +@Component +class CourseWriter( + private val courseRepository: CourseRepository, +) { + fun save(course: Course): Course { + return courseRepository.save(course) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/course/storage/CourseRepositoryImpl.kt b/src/main/kotlin/com/yourssu/soongpt/domain/course/storage/CourseRepositoryImpl.kt index 0a291c4..5cc3d39 100644 --- a/src/main/kotlin/com/yourssu/soongpt/domain/course/storage/CourseRepositoryImpl.kt +++ b/src/main/kotlin/com/yourssu/soongpt/domain/course/storage/CourseRepositoryImpl.kt @@ -33,6 +33,13 @@ class CourseRepositoryImpl( .fetch() .map { it.toDomain() } } + + override fun getAll(ids: List): List { + return jpaQueryFactory.selectFrom(courseEntity) + .where(courseEntity.id.`in`(ids)) + .fetch() + .map { it.toDomain() } + } } interface CourseJpaRepository : JpaRepository { diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/application/TimetableController.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/application/TimetableController.kt new file mode 100644 index 0000000..69fe53b --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/application/TimetableController.kt @@ -0,0 +1,22 @@ +package com.yourssu.soongpt.domain.timetable.application + +import com.yourssu.soongpt.common.business.dto.Response +import com.yourssu.soongpt.domain.timetable.business.TimetableService +import com.yourssu.soongpt.domain.timetable.business.dto.TimetableResponse +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/timetables") +class TimetableController( + private val timetableService: TimetableService, +) { + @GetMapping("/{id}") + fun getTimetable(@PathVariable id: Long): ResponseEntity> { + val response = timetableService.getTimeTable(id) + return ResponseEntity.ok(Response(result=response)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/TimetableService.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/TimetableService.kt new file mode 100644 index 0000000..c48887d --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/TimetableService.kt @@ -0,0 +1,22 @@ +package com.yourssu.soongpt.domain.timetable.business + +import com.yourssu.soongpt.domain.courseTime.implement.CourseTimeReader +import com.yourssu.soongpt.domain.timetable.business.dto.TimetableCourseResponse +import com.yourssu.soongpt.domain.timetable.business.dto.TimetableResponse +import com.yourssu.soongpt.domain.timetable.implement.TimetableCourseReader +import com.yourssu.soongpt.domain.timetable.implement.TimetableReader +import org.springframework.stereotype.Service + +@Service +class TimetableService( + private val timetableReader: TimetableReader, + private val timetableCourseReader: TimetableCourseReader, + private val courseTimeReader: CourseTimeReader, +) { + fun getTimeTable(id: Long): TimetableResponse { + val timetable = timetableReader.get(id) + val courses = timetableCourseReader.findAllCourseByTimetableId(id) + val response = courses.map { TimetableCourseResponse.from(it, courseTimeReader.findAllByCourseId(it.id!!)) } + return TimetableResponse.from(timetable, response) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/dto/TimetableCourseResponse.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/dto/TimetableCourseResponse.kt new file mode 100644 index 0000000..a9ef3da --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/dto/TimetableCourseResponse.kt @@ -0,0 +1,27 @@ +package com.yourssu.soongpt.domain.timetable.business.dto + +import com.yourssu.soongpt.domain.course.implement.Course +import com.yourssu.soongpt.domain.courseTime.business.dto.CourseTimeResponse +import com.yourssu.soongpt.domain.courseTime.implement.CourseTime + +data class TimetableCourseResponse( + val courseName: String, + val professorName: String?, + val classification: String, + val courseCode: Int, + val credit: Int, + val courseTime: List, +) { + companion object { + fun from(course: Course, courseTimes: List): TimetableCourseResponse { + return TimetableCourseResponse( + courseName = course.courseName, + professorName = course.professorName, + classification = course.classification.name, + courseCode = course.courseCode, + credit = course.credit, + courseTime = courseTimes.map { CourseTimeResponse.from(it) }, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/dto/TimetableResponse.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/dto/TimetableResponse.kt new file mode 100644 index 0000000..b911c02 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/business/dto/TimetableResponse.kt @@ -0,0 +1,19 @@ +package com.yourssu.soongpt.domain.timetable.business.dto + +import com.yourssu.soongpt.domain.timetable.implement.Timetable + +data class TimetableResponse( + val timetableId: Long, + val tag: String, + val courses: List, +) { + companion object { + fun from(timetable: Timetable, courses: List): TimetableResponse { + return TimetableResponse( + timetableId = timetable.id!!, + tag = timetable.tag.name, + courses = courses, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/Tag.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/Tag.kt new file mode 100644 index 0000000..df1ccf3 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/Tag.kt @@ -0,0 +1,6 @@ +package com.yourssu.soongpt.domain.timetable.implement + +enum class Tag { + DEFAULT, + ; +} diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/Timetable.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/Timetable.kt new file mode 100644 index 0000000..40be99c --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/Timetable.kt @@ -0,0 +1,7 @@ +package com.yourssu.soongpt.domain.timetable.implement + +class Timetable( + val id: Long? = null, + val tag: Tag, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourse.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourse.kt new file mode 100644 index 0000000..4ad13b1 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourse.kt @@ -0,0 +1,8 @@ +package com.yourssu.soongpt.domain.timetable.implement + +class TimetableCourse( + val id: Long? = null, + val timetableId: Long, + val courseId: Long, +) { +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseReader.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseReader.kt new file mode 100644 index 0000000..35bfabc --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseReader.kt @@ -0,0 +1,16 @@ +package com.yourssu.soongpt.domain.timetable.implement + +import com.yourssu.soongpt.domain.course.implement.Course +import com.yourssu.soongpt.domain.course.implement.CourseRepository +import org.springframework.stereotype.Component + +@Component +class TimetableCourseReader( + private val timetableCourseRepository: TimetableCourseRepository, + private val courseRepository: CourseRepository, +) { + fun findAllCourseByTimetableId(timetableId: Long): List { + val courseIds = timetableCourseRepository.findAllCourseByTimetableId(timetableId).map { it.courseId } + return courseRepository.getAll(courseIds) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseRepository.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseRepository.kt new file mode 100644 index 0000000..2ae5624 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseRepository.kt @@ -0,0 +1,6 @@ +package com.yourssu.soongpt.domain.timetable.implement + +interface TimetableCourseRepository { + fun save(timetableCourse: TimetableCourse): TimetableCourse + fun findAllCourseByTimetableId(id: Long): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseWriter.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseWriter.kt new file mode 100644 index 0000000..62e6bc2 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableCourseWriter.kt @@ -0,0 +1,12 @@ +package com.yourssu.soongpt.domain.timetable.implement + +import org.springframework.stereotype.Component + +@Component +class TimetableCourseWriter( + private val timetableCourseRepository: TimetableCourseRepository, +) { + fun save(timetableCourse: TimetableCourse): TimetableCourse { + return timetableCourseRepository.save(timetableCourse) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableReader.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableReader.kt new file mode 100644 index 0000000..8d73c7d --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableReader.kt @@ -0,0 +1,12 @@ +package com.yourssu.soongpt.domain.timetable.implement + +import org.springframework.stereotype.Component + +@Component +class TimetableReader( + private val timetableRepository: TimetableRepository, +) { + fun get(id: Long): Timetable { + return timetableRepository.get(id) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableRepository.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableRepository.kt new file mode 100644 index 0000000..9e3a82f --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableRepository.kt @@ -0,0 +1,6 @@ +package com.yourssu.soongpt.domain.timetable.implement + +interface TimetableRepository { + fun save(timetable: Timetable): Timetable + fun get(id: Long): Timetable +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableWriter.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableWriter.kt new file mode 100644 index 0000000..efb4886 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/implement/TimetableWriter.kt @@ -0,0 +1,12 @@ +package com.yourssu.soongpt.domain.timetable.implement + +import org.springframework.stereotype.Component + +@Component +class TimetableWriter( + private val timetableRepository: TimetableRepository, +) { + fun save(timetable: Timetable): Timetable { + return timetableRepository.save(timetable) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimeTableRepositoryImpl.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimeTableRepositoryImpl.kt new file mode 100644 index 0000000..b74df9e --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimeTableRepositoryImpl.kt @@ -0,0 +1,23 @@ +package com.yourssu.soongpt.domain.timetable.storage + +import com.yourssu.soongpt.domain.timetable.implement.Timetable +import com.yourssu.soongpt.domain.timetable.implement.TimetableRepository +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +class TimetableRepositoryImpl( + private val timetableJpaRepository: TimetableJpaRepository, +): TimetableRepository { + override fun save(timetable: Timetable): Timetable { + return timetableJpaRepository.save(TimetableEntity.from(timetable)) + .toDomain() + } + + override fun get(id: Long): Timetable { + return timetableJpaRepository.findById(id).get().toDomain() + } +} + +interface TimetableJpaRepository: JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableCourseEntity.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableCourseEntity.kt new file mode 100644 index 0000000..6f57c4a --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableCourseEntity.kt @@ -0,0 +1,36 @@ +package com.yourssu.soongpt.domain.timetable.storage + +import com.yourssu.soongpt.domain.timetable.implement.TimetableCourse +import jakarta.persistence.* + +@Entity +@Table(name = "timetable_course") +class TimetableCourseEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(nullable = false) + val timetableId: Long, + + @Column(nullable = false) + val courseId: Long, +) { + companion object { + fun from(timetableCourse: TimetableCourse): TimetableCourseEntity { + return TimetableCourseEntity( + id = timetableCourse.id, + timetableId = timetableCourse.timetableId, + courseId = timetableCourse.courseId + ) + } + } + + fun toDomain(): TimetableCourse { + return TimetableCourse( + id = id, + timetableId = timetableId, + courseId = courseId + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableCourseRepositoryImpl.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableCourseRepositoryImpl.kt new file mode 100644 index 0000000..32de6cf --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableCourseRepositoryImpl.kt @@ -0,0 +1,32 @@ +package com.yourssu.soongpt.domain.timetable.storage + +import com.querydsl.jpa.impl.JPAQueryFactory +import com.yourssu.soongpt.domain.timetable.implement.TimetableCourse +import com.yourssu.soongpt.domain.timetable.implement.TimetableCourseRepository +import com.yourssu.soongpt.domain.timetable.storage.QTimetableCourseEntity.timetableCourseEntity +import com.yourssu.soongpt.domain.timetable.storage.QTimetableEntity.timetableEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +class TimetableCourseRepositoryImpl( + private val timetableCourseJpaRepository: TimetableCourseJpaRepository, + private val jpaQueryFactory: JPAQueryFactory, +): TimetableCourseRepository { + override fun save(timetableCourse: TimetableCourse): TimetableCourse { + return timetableCourseJpaRepository.save(TimetableCourseEntity.from(timetableCourse)) + .toDomain() + } + + override fun findAllCourseByTimetableId(id: Long): List { + return jpaQueryFactory.selectFrom(timetableCourseEntity) + .join(timetableEntity) + .on(timetableCourseEntity.timetableId.eq(timetableEntity.id)) + .where(timetableEntity.id.eq(id)) + .fetch() + .map { it.toDomain() } + } +} + +interface TimetableCourseJpaRepository: JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableEntity.kt b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableEntity.kt new file mode 100644 index 0000000..3b0e2c6 --- /dev/null +++ b/src/main/kotlin/com/yourssu/soongpt/domain/timetable/storage/TimetableEntity.kt @@ -0,0 +1,33 @@ +package com.yourssu.soongpt.domain.timetable.storage + +import com.yourssu.soongpt.domain.timetable.implement.Tag +import com.yourssu.soongpt.domain.timetable.implement.Timetable +import jakarta.persistence.* + +@Entity +@Table(name = "timetable") +class TimetableEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + val tag: Tag, +) { + companion object { + fun from(timetable: Timetable): TimetableEntity { + return TimetableEntity( + id = timetable.id, + tag = timetable.tag + ) + } + } + + fun toDomain(): Timetable { + return Timetable( + id = id, + tag = tag + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/yourssu/soongpt/common/support/fixture/TimetableFixture.kt b/src/test/kotlin/com/yourssu/soongpt/common/support/fixture/TimetableFixture.kt new file mode 100644 index 0000000..1186c56 --- /dev/null +++ b/src/test/kotlin/com/yourssu/soongpt/common/support/fixture/TimetableFixture.kt @@ -0,0 +1,15 @@ +package com.yourssu.soongpt.common.support.fixture + +import com.yourssu.soongpt.domain.timetable.implement.Tag +import com.yourssu.soongpt.domain.timetable.implement.Timetable + +enum class TimetableFixture( + val tag: Tag, +) { + DEFAULT(Tag.DEFAULT), + ; + + fun toDomain() = Timetable( + tag = tag, + ) +} \ No newline at end of file diff --git a/src/test/kotlin/com/yourssu/soongpt/domain/timetable/business/TimetableServiceTest.kt b/src/test/kotlin/com/yourssu/soongpt/domain/timetable/business/TimetableServiceTest.kt new file mode 100644 index 0000000..405c40c --- /dev/null +++ b/src/test/kotlin/com/yourssu/soongpt/domain/timetable/business/TimetableServiceTest.kt @@ -0,0 +1,63 @@ +package com.yourssu.soongpt.domain.timetable.business + +import com.yourssu.soongpt.common.support.config.ApplicationTest +import com.yourssu.soongpt.common.support.fixture.CourseFixture +import com.yourssu.soongpt.common.support.fixture.CourseTimeFixture +import com.yourssu.soongpt.common.support.fixture.TimetableFixture +import com.yourssu.soongpt.domain.course.implement.CourseWriter +import com.yourssu.soongpt.domain.courseTime.implement.CourseTimeWriter +import com.yourssu.soongpt.domain.timetable.implement.TimetableCourse +import com.yourssu.soongpt.domain.timetable.implement.TimetableCourseWriter +import com.yourssu.soongpt.domain.timetable.implement.TimetableWriter +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.* +import org.springframework.beans.factory.annotation.Autowired + +@ApplicationTest +class TimetableServiceTest( + +) { + @Autowired + lateinit var timetableService: TimetableService + + @Autowired + lateinit var timetableWriter: TimetableWriter + + @Autowired + lateinit var timetableCourseWriter: TimetableCourseWriter + + @Autowired + lateinit var courseWriter: CourseWriter + + @Autowired + lateinit var courseTimeWriter: CourseTimeWriter + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores::class) + inner class getTimeTable_메서드는 { + var timetableId: Long? = null + + @BeforeEach + fun setUp() { + val course = courseWriter.save(CourseFixture.MAJOR_REQUIRED.toDomainRandomCourseCode()) + val timetable = timetableWriter.save(TimetableFixture.DEFAULT.toDomain()) + courseTimeWriter.save(CourseTimeFixture.MONDAY_17_19.toDomain(course.id!!)) + timetableId = timetable.id + timetableCourseWriter.save(TimetableCourse(timetableId = timetable.id!!, courseId = course.id!!)) + } + + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores::class) + inner class 시간표_아이디를_받으면 { + @Test + @DisplayName("시간표에 소속된 모든 과목을 반환한다.") + fun success() { + val timeTable = timetableService.getTimeTable(timetableId!!) + val courseTime = timeTable.courses.first().courseTime + + assertThat(courseTime).hasSize(1) + } + } + } +} \ No newline at end of file