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

Feat: 과목 선정 로직 구현 - 1단계 #27

Merged
merged 9 commits into from
Feb 5, 2025
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