Skip to content

Commit

Permalink
Feat: 과목 선정 로직 구현 - 1단계 (#27)
Browse files Browse the repository at this point in the history
Closes #21 

다음 사항 구현했습니다.
- 전공 필수, 교양 필수(채플 포함), 전공 선택(사용자가 선택한 것만) 이름으로 조회하기
- 3가지 분야의 과목들을 가지고, 시간표가 성립 가능한 모든 경우의 수 생성(성립이 되는 경우가 하나도 없다면 오류 반환)
  • Loading branch information
DWL21 authored Feb 5, 2025
2 parents dda4217 + f6874c9 commit 18d0199
Show file tree
Hide file tree
Showing 26 changed files with 423 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yourssu.soongpt.domain.course.storage
package com.yourssu.soongpt.domain.course

import com.yourssu.soongpt.domain.course.implement.Classification
import com.yourssu.soongpt.domain.course.implement.Course
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.yourssu.soongpt.domain.course.storage
package com.yourssu.soongpt.domain.course

import com.querydsl.jpa.impl.JPAQueryFactory
import com.yourssu.soongpt.domain.course.QCourseEntity.courseEntity
import com.yourssu.soongpt.domain.course.implement.Classification
import com.yourssu.soongpt.domain.course.implement.Course
import com.yourssu.soongpt.domain.course.implement.CourseRepository
import com.yourssu.soongpt.domain.course.storage.QCourseEntity.courseEntity
import com.yourssu.soongpt.domain.course.implement.Courses
import com.yourssu.soongpt.domain.departmentGrade.storage.QDepartmentGradeEntity.departmentGradeEntity
import com.yourssu.soongpt.domain.target.storage.QTargetEntity.targetEntity
import org.springframework.data.jpa.repository.JpaRepository
Expand Down Expand Up @@ -40,6 +41,26 @@ class CourseRepositoryImpl(
.fetch()
.map { it.toDomain() }
}

override fun findByDepartmentIdAndCourseName(
departmentId: Long,
courseName: String,
classification: Classification
): Courses {
val courses = jpaQueryFactory.selectFrom(courseEntity)
.innerJoin(targetEntity)
.on(courseEntity.id.eq(targetEntity.courseId))
.innerJoin(departmentGradeEntity)
.on(targetEntity.departmentGradeId.eq(departmentGradeEntity.id))
.where(
departmentGradeEntity.departmentId.eq(departmentId),
courseEntity.courseName.eq(courseName),
courseEntity.classification.eq(classification)
)
.fetch()
.map { it.toDomain() }
return Courses(courses)
}
}

interface CourseJpaRepository : JpaRepository<CourseEntity, Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import com.yourssu.soongpt.domain.courseTime.implement.CourseTimeReader
import com.yourssu.soongpt.domain.courseTime.implement.CourseTimeWriter
import com.yourssu.soongpt.domain.department.implement.DepartmentReader
import com.yourssu.soongpt.domain.departmentGrade.implement.DepartmentGradeReader
import com.yourssu.soongpt.domain.target.implement.Target
import com.yourssu.soongpt.domain.target.implement.TargetReader
import com.yourssu.soongpt.domain.target.implement.TargetWriter
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import com.yourssu.soongpt.domain.target.implement.Target
import com.yourssu.soongpt.domain.target.implement.TargetWriter

@Service
class CourseService(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.yourssu.soongpt.domain.course.implement

import com.yourssu.soongpt.domain.course.implement.exception.CourseNotFoundException
import org.springframework.stereotype.Component

@Component
Expand All @@ -17,4 +18,24 @@ class CourseReader(
fun findAllByDepartmentIdInGeneralRequired(departmentId: Long): List<Course> {
return courseRepository.findAllByDepartmentId(departmentId, Classification.GENERAL_REQUIRED)
}

fun findAllByCourseNameInMajorRequired(departmentId: Long, courseName: String): Courses {
return findAllByCourseName(departmentId, courseName, Classification.MAJOR_REQUIRED)
}

fun findAllByCourseNameInMajorElective(departmentId: Long, courseName: String): Courses {
return findAllByCourseName(departmentId, courseName, Classification.MAJOR_ELECTIVE)
}

fun findAllByCourseNameInGeneralRequired(departmentId: Long, courseName: String): Courses {
return findAllByCourseName(departmentId, courseName, Classification.GENERAL_REQUIRED)
}

private fun findAllByCourseName(departmentId: Long, courseName: String, classification: Classification): Courses {
val courses = courseRepository.findByDepartmentIdAndCourseName(departmentId, courseName, classification)
if (courses.isEmpty()) {
throw CourseNotFoundException(courseName = courseName)
}
return courses
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ interface CourseRepository {
fun save(course: Course): Course
fun findAllByDepartmentId(departmentId: Long, classification: Classification): List<Course>
fun getAll(ids: List<Long>): List<Course>
fun findByDepartmentIdAndCourseName(departmentId: Long, courseName: String, classification: Classification): Courses
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yourssu.soongpt.domain.course.implement

class Courses(
val courses: List<Course>,
) {
fun isEmpty(): Boolean {
return courses.isEmpty()
}

fun unpackNameAndProfessor(): List<Pair<String, String>> {
return courses.map { it.courseName to it.professorName!! }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.yourssu.soongpt.domain.course.implement

import com.yourssu.soongpt.domain.course.implement.exception.InvalidTimetableRequestException
import org.springframework.stereotype.Component

@Component
class CoursesFactory {
fun generateTimetableCandidates(
coursesCandidates: List<Courses>
): List<Courses> {
return coursesCandidates.fold(listOf(emptyList<Course>())) { acc, courses ->
acc.flatMap { currentCombination ->
courses.courses.map { course -> currentCombination + course }
}
}.map { Courses(it) }
}

fun validateEmpty(timetableCandidates: List<Courses>) {
if (timetableCandidates.isEmpty()) {
throw InvalidTimetableRequestException()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yourssu.soongpt.domain.course.implement.exception

import com.yourssu.soongpt.common.handler.NotFoundException

class CourseNotFoundException(
val courseName: String = "",
) : NotFoundException(message = "해당하는 과목이 없습니다. 과목 이름: {$courseName}") {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.yourssu.soongpt.domain.course.implement.exception

class InvalidCoursesException : RuntimeException()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.yourssu.soongpt.domain.course.implement.exception

import com.yourssu.soongpt.common.handler.BadRequestException

class InvalidTimetableRequestException : BadRequestException(message = "시간표가 나올 수 있는 경우의 수가 없습니다.") {

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.yourssu.soongpt.domain.rating.application.dto

import com.yourssu.soongpt.domain.rating.business.RatingCreatedCommand

data class RatingCreatedRequest(
val course: String,
val professor: String,
val star: Double,
val point: Double
) {
fun toCommand(): RatingCreatedCommand {
return RatingCreatedCommand(
course = course,
professor = professor,
star = star,
point = point
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.yourssu.soongpt.domain.rating.business

import com.yourssu.soongpt.domain.rating.implement.Rating

class RatingCreatedCommand(
val course: String,
val professor: String,
val star: Double,
val point: Double,
) {
fun toRating(): Rating {
return Rating(
courseName = course,
professorName = professor,
star = star,
point = point,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.yourssu.soongpt.domain.rating.business.dto

import com.yourssu.soongpt.domain.rating.implement.Rating

data class RatingResponse(
val course: String,
val professor: String,
val star: Double,
val point: Double,
) {
companion object {
fun from(rating: Rating): RatingResponse {
return RatingResponse(
course = rating.courseName,
professor = rating.professorName,
star = rating.star,
point = rating.point,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Rating(
val id: Long? = null,
val courseName: String,
val professorName: String,
val star: Int,
val star: Double,
var point: Double = 50.0,
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.yourssu.soongpt.domain.rating.implement

import com.yourssu.soongpt.domain.course.implement.Courses
import com.yourssu.soongpt.domain.rating.implement.exception.RatingNotFoundByCourseNameException
import org.springframework.stereotype.Component

@Component
class RatingReader(
private val ratingRepository: RatingRepository,
) {
fun findAllBy(courses: Courses): List<Rating> {
return courses.unpackNameAndProfessor().mapNotNull { (courseName, professorName) ->
ratingRepository.findByCourseNameAndProfessorName(courseName, professorName)
}
}


fun findBy(courseName: String, professorName: String): Rating {
return ratingRepository.findByCourseNameAndProfessorName(courseName, professorName)
?: throw RatingNotFoundByCourseNameException()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class RatingEntity(
val professorName: String,

@Column(name = "star", nullable = false)
val star: Int,
val star: Double,

@Column(name = "point", nullable = false)
val point: Double,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.yourssu.soongpt.domain.timetable.application

import com.yourssu.soongpt.common.business.dto.Response
import com.yourssu.soongpt.domain.timetable.application.dto.TimetableCreatedRequest
import com.yourssu.soongpt.domain.timetable.business.TimetableService
import com.yourssu.soongpt.domain.timetable.business.dto.TimetableResponse
import com.yourssu.soongpt.domain.timetable.business.dto.TimetableResponses
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
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/timetables")
class TimetableController(
private val timetableService: TimetableService,
) {
@PostMapping
fun createTimetable(@RequestBody request: TimetableCreatedRequest): ResponseEntity<Response<TimetableResponses>> {
val responses = timetableService.createTimetable(request.toCommand())
return ResponseEntity.ok(Response(result=responses))
}


@GetMapping("/{id}")
fun getTimetable(@PathVariable id: Long): ResponseEntity<Response<TimetableResponse>> {
val response = timetableService.getTimeTable(id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.yourssu.soongpt.domain.timetable.application.dto

import com.yourssu.soongpt.domain.timetable.business.dto.TimetableCreatedCommand
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import org.hibernate.validator.constraints.Range

data class TimetableCreatedRequest(
@field:Range(min = 15, max = 25, message = "학번은 15부터 25까지 가능합니다.")
val schoolId: Int,

@field:NotBlank(message = "학과는 필수 입력값입니다.")
val department: String,

@field:Range(min = 1, max = 5, message = "학년은 1부터 5까지 가능합니다.")
val grade: Int,

val isChapel: Boolean = false,

@field:NotNull
val majorRequiredCourses: List<String>,

@field:NotNull
val majorElectiveCourses: List<String>,

@field:NotNull
val generalRequiredCourses: List<String>,
) {
fun toCommand(): TimetableCreatedCommand {
return TimetableCreatedCommand(
departmentName = department,
grade = grade,
isChapel = isChapel,
majorRequiredCourses = majorRequiredCourses,
majorElectiveCourses = majorElectiveCourses,
generalRequiredCourses = generalRequiredCourses,
)
}
}
Loading

0 comments on commit 18d0199

Please sign in to comment.