From 4777fe7c4d129f1b17cbbdcff060884f5fd266e8 Mon Sep 17 00:00:00 2001 From: Hankyeol Choi Date: Thu, 4 Apr 2024 23:21:29 +0900 Subject: [PATCH] =?UTF-8?q?snutt=20=EC=A0=84=EB=8B=AC=EC=9A=A9=20=EA=B0=95?= =?UTF-8?q?=EC=9D=98=ED=8F=89=EC=A0=90=20api=20=EC=83=9D=EC=84=B1=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * mongoItemCursorReader로 변경 - spring batch 5.1.1 적용된 최신 spring boot로 업데이트 - MongoItemReader->MongoItemCursorReader - kotlin 플러그인 업데이트 * snutt 전달용 강의평점 api 생성 * ktlint 적용 * Fix typo * 리뷰 반영 --- .../snuttev/controller/EvaluationController.kt | 6 +++--- .../snuttev/controller/LectureController.kt | 13 +++++++++++-- .../snuttev/sync/SnuttLectureSyncJobConfig.kt | 18 ++++++++---------- build.gradle.kts | 10 +++++----- .../core/domain/lecture/dto/LectureResponse.kt | 6 ++++++ .../core/domain/lecture/model/Lecture.kt | 5 +++++ .../domain/lecture/model/SemesterLecture.kt | 9 ++++++++- .../lecture/repository/LectureRepository.kt | 10 ++++++++++ .../repository/SemesterLectureRepository.kt | 3 +++ .../domain/lecture/service/LectureService.kt | 14 ++++++++++++++ .../db/V4__semester_lecture_snutt_id.sql | 2 ++ 11 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 core/src/main/resources/db/V4__semester_lecture_snutt_id.sql diff --git a/api/src/main/kotlin/com/wafflestudio/snuttev/controller/EvaluationController.kt b/api/src/main/kotlin/com/wafflestudio/snuttev/controller/EvaluationController.kt index 25b3567..c6702a7 100644 --- a/api/src/main/kotlin/com/wafflestudio/snuttev/controller/EvaluationController.kt +++ b/api/src/main/kotlin/com/wafflestudio/snuttev/controller/EvaluationController.kt @@ -58,7 +58,7 @@ class EvaluationController( @GetMapping("/v1/lectures/{id}/evaluations") fun getLectureEvaluations( @PathVariable(value = "id") lectureId: Long, - @RequestParam("cursor") cursor: String?, + @RequestParam cursor: String?, @RequestAttribute(value = "UserId") userId: String, ): CursorPaginationResponse { return evaluationService.getEvaluationsOfLecture(userId, lectureId, cursor) @@ -74,7 +74,7 @@ class EvaluationController( @GetMapping("/v1/evaluations/users/me") fun getEvaluationsOfMe( - @RequestParam("cursor") cursor: String?, + @RequestParam cursor: String?, @RequestAttribute(value = "UserId") userId: String, ): CursorPaginationResponse { return evaluationService.getMyEvaluations(userId, cursor) @@ -83,7 +83,7 @@ class EvaluationController( @GetMapping("/v1/tags/main/{id}/evaluations") fun getMainTagEvaluations( @PathVariable(value = "id") tagId: Long, - @RequestParam("cursor") cursor: String?, + @RequestParam cursor: String?, @RequestAttribute(value = "UserId") userId: String, ): CursorPaginationResponse { return evaluationService.getMainTagEvaluations(userId, tagId, cursor) diff --git a/api/src/main/kotlin/com/wafflestudio/snuttev/controller/LectureController.kt b/api/src/main/kotlin/com/wafflestudio/snuttev/controller/LectureController.kt index db5b9a9..014cda3 100644 --- a/api/src/main/kotlin/com/wafflestudio/snuttev/controller/LectureController.kt +++ b/api/src/main/kotlin/com/wafflestudio/snuttev/controller/LectureController.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.wafflestudio.snuttev.core.common.dto.common.ListResponse import com.wafflestudio.snuttev.core.common.dto.common.PaginationResponse +import com.wafflestudio.snuttev.core.domain.lecture.dto.EvLectureSummaryForSnutt import com.wafflestudio.snuttev.core.domain.lecture.dto.LectureAndSemesterLecturesResponse import com.wafflestudio.snuttev.core.domain.lecture.dto.LectureDto import com.wafflestudio.snuttev.core.domain.lecture.dto.LectureIdResponse @@ -41,17 +42,25 @@ class LectureController( @GetMapping("/v1/lectures/id") fun getLectureId( @RequestParam("course_number") courseNumber: String, - @RequestParam("instructor") instructor: String, + @RequestParam instructor: String, ): LectureIdResponse { return lectureService.getLectureIdFromCourseNumber(courseNumber, instructor) } + @GetMapping("/v1/lectures/snutt-summary") + fun getEvLectureSummaryForSnutt( + @RequestParam semesterLectureSnuttIds: List, + ): ListResponse { + val evLectureSummary = lectureService.getEvLectureSummaryForSnutt(semesterLectureSnuttIds) + return ListResponse(evLectureSummary) + } + @GetMapping("/v1/users/me/lectures/latest") fun getLecturesTakenByCurrentUser( @Parameter(hidden = true) @RequestParam("snutt_lecture_info") snuttLectureInfoString: String? = "", - @RequestParam("filter") + @RequestParam filter: String?, @RequestAttribute(value = "UserId") userId: String, diff --git a/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt b/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt index f5ee967..1084b70 100644 --- a/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt +++ b/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt @@ -1,13 +1,11 @@ package com.wafflestudio.snuttev.sync import com.wafflestudio.snuttev.core.common.type.LectureClassification -import com.wafflestudio.snuttev.core.common.util.SemesterUtils import com.wafflestudio.snuttev.core.domain.lecture.model.Lecture import com.wafflestudio.snuttev.core.domain.lecture.model.SemesterLecture import com.wafflestudio.snuttev.core.domain.lecture.repository.LectureRepository import com.wafflestudio.snuttev.core.domain.lecture.repository.SemesterLectureRepository import com.wafflestudio.snuttev.sync.model.SnuttSemesterLecture -import com.wafflestudio.snuttev.sync.repository.SnuttSemesterLectureRepository import jakarta.persistence.EntityManagerFactory import org.springframework.batch.core.Job import org.springframework.batch.core.Step @@ -16,8 +14,8 @@ import org.springframework.batch.core.repository.JobRepository import org.springframework.batch.core.step.builder.StepBuilder import org.springframework.batch.item.ItemProcessor import org.springframework.batch.item.ItemWriter -import org.springframework.batch.item.data.MongoItemReader -import org.springframework.batch.item.data.builder.MongoItemReaderBuilder +import org.springframework.batch.item.data.MongoCursorItemReader +import org.springframework.batch.item.data.builder.MongoCursorItemReaderBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Profile @@ -36,8 +34,6 @@ class SnuttLectureSyncJobConfig( private val mongoTemplate: MongoTemplate, private val semesterLectureRepository: SemesterLectureRepository, private val lectureRepository: LectureRepository, - private val semesterUtils: SemesterUtils, - private val snuttSemesterLectureRepository: SnuttSemesterLectureRepository, ) { companion object { private const val JOB_NAME = "SYNC_JOB" @@ -104,12 +100,12 @@ class SnuttLectureSyncJobConfig( .build() } - private fun reader(query: Query): MongoItemReader { - return MongoItemReaderBuilder() + private fun reader(query: Query): MongoCursorItemReader { + return MongoCursorItemReaderBuilder() .template(mongoTemplate) .collection("lectures").query(query) - .sorts(mapOf("courseNumber" to Sort.DEFAULT_DIRECTION)) - .targetType(SnuttSemesterLecture::class.java).pageSize(CHUNK_SIZE) + .sorts(mapOf("_id" to Sort.DEFAULT_DIRECTION)) + .targetType(SnuttSemesterLecture::class.java) .name(this::reader.name) .build() } @@ -138,6 +134,7 @@ class SnuttLectureSyncJobConfig( this.extraInfo = item.remark this.lecture = lecture this.credit = item.credit + this.snuttId = item.id } ?: SemesterLecture( lecture, item.year, @@ -147,6 +144,7 @@ class SnuttLectureSyncJobConfig( item.academicYear, item.category, LectureClassification.customValueOf(item.classification)!!, + item.id, ).also { semesterLecturesMap["${item.courseNumber},${item.instructor},${item.year},${item.semester}"] = it } } } diff --git a/build.gradle.kts b/build.gradle.kts index bc038a5..ae4aa3a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,12 +4,12 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar import java.io.ByteArrayOutputStream plugins { - id("org.springframework.boot") version "3.1.4" apply false + id("org.springframework.boot") version "3.2.4" apply false id("io.spring.dependency-management") version "1.1.3" - kotlin("jvm") version "1.9.10" - kotlin("plugin.spring") version "1.8.21" - kotlin("plugin.allopen") version "1.8.21" - kotlin("plugin.noarg") version "1.8.21" + kotlin("jvm") version "1.9.22" + kotlin("plugin.spring") version "1.9.22" + kotlin("plugin.allopen") version "1.9.22" + kotlin("plugin.noarg") version "1.9.22" id("org.jlleitschuh.gradle.ktlint") version "11.3.2" } diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/dto/LectureResponse.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/dto/LectureResponse.kt index 4a28461..501f43f 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/dto/LectureResponse.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/dto/LectureResponse.kt @@ -50,3 +50,9 @@ data class LectureTakenByUserResponse( val takenYear: Int, val takenSemester: Int, ) + +data class EvLectureSummaryForSnutt( + val snuttId: String, + val evLectureId: Long, + val avgRating: Double?, +) diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/Lecture.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/Lecture.kt index c22e1ff..629a155 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/Lecture.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/Lecture.kt @@ -61,3 +61,8 @@ data class LectureEvaluationSummaryDao( val avgLifeBalance: Double?, val avgRating: Double?, ) + +data class LectureRatingDao( + val id: Long, + val avgRating: Double?, +) diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/SemesterLecture.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/SemesterLecture.kt index 5dcfd97..0a46638 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/SemesterLecture.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/model/SemesterLecture.kt @@ -7,6 +7,7 @@ import jakarta.persistence.Column import jakarta.persistence.Convert import jakarta.persistence.Entity import jakarta.persistence.FetchType +import jakarta.persistence.Index import jakarta.persistence.JoinColumn import jakarta.persistence.ManyToOne import jakarta.persistence.OneToMany @@ -14,7 +15,10 @@ import jakarta.persistence.Table import jakarta.persistence.UniqueConstraint @Entity -@Table(uniqueConstraints = [UniqueConstraint(columnNames = ["lecture_id", "year", "semester"])]) +@Table( + uniqueConstraints = [UniqueConstraint(columnNames = ["lecture_id", "year", "semester"])], + indexes = [Index(name = "semester_lecture_snutt_id_index", columnList = "snutt_id")], +) class SemesterLecture( @ManyToOne(optional = false, fetch = FetchType.LAZY) @JoinColumn(name = "lecture_id", nullable = false) @@ -37,6 +41,9 @@ class SemesterLecture( @Convert(converter = LectureClassificationConverter::class) var classification: LectureClassification, + @Column(name = "snutt_id", columnDefinition = "char(24)") + var snuttId: String? = null, + @OneToMany(mappedBy = "semesterLecture") val evaluations: List = listOf(), ) : BaseEntity() diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/LectureRepository.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/LectureRepository.kt index 2e830bf..9baa36a 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/LectureRepository.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/LectureRepository.kt @@ -2,6 +2,7 @@ package com.wafflestudio.snuttev.core.domain.lecture.repository import com.wafflestudio.snuttev.core.domain.lecture.model.Lecture import com.wafflestudio.snuttev.core.domain.lecture.model.LectureEvaluationSummaryDao +import com.wafflestudio.snuttev.core.domain.lecture.model.LectureRatingDao import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query @@ -22,4 +23,13 @@ interface LectureRepository : JpaRepository, LectureRepositoryCu """, ) fun findLectureWithAvgEvById(id: Long): LectureEvaluationSummaryDao + + @Query( + """ + select new com.wafflestudio.snuttev.core.domain.lecture.model.LectureRatingDao( + sl.lecture.id, avg(le.rating) + ) from LectureEvaluation le right join le.semesterLecture sl where sl.lecture.id in :ids and le.isHidden = false group by sl.lecture.id + """, + ) + fun findAllRatingsByLectureIds(ids: List): List } diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/SemesterLectureRepository.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/SemesterLectureRepository.kt index 24c3f2d..40c7cf4 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/SemesterLectureRepository.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/repository/SemesterLectureRepository.kt @@ -27,4 +27,7 @@ interface SemesterLectureRepository : JpaRepository { fun findAllByLectureIdOrderByYearDescSemesterDesc(lectureId: Long): List fun findByYearAndSemesterAndLecture(year: Int, semester: Int, lecture: Lecture): SemesterLecture? + + @Query("SELECT sl FROM SemesterLecture sl INNER JOIN FETCH Lecture l ON sl.lecture = l WHERE sl.snuttId IN :snuttIds") + fun findAllBySnuttIds(snuttIds: List): List } diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/service/LectureService.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/service/LectureService.kt index 6ea7d62..f311c65 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/service/LectureService.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/lecture/service/LectureService.kt @@ -5,6 +5,7 @@ import com.wafflestudio.snuttev.core.common.error.LectureNotFoundException import com.wafflestudio.snuttev.core.common.util.SemesterUtils import com.wafflestudio.snuttev.core.domain.evaluation.dto.SemesterLectureDto import com.wafflestudio.snuttev.core.domain.evaluation.repository.LectureEvaluationRepository +import com.wafflestudio.snuttev.core.domain.lecture.dto.EvLectureSummaryForSnutt import com.wafflestudio.snuttev.core.domain.lecture.dto.LectureAndSemesterLecturesResponse import com.wafflestudio.snuttev.core.domain.lecture.dto.LectureDto import com.wafflestudio.snuttev.core.domain.lecture.dto.LectureIdResponse @@ -118,6 +119,19 @@ class LectureService( return LectureIdResponse(lecture.id!!) } + fun getEvLectureSummaryForSnutt(semesterLectureSnuttIds: List): List { + val lectures = semesterLectureRepository.findAllBySnuttIds(semesterLectureSnuttIds) + val ratingMap = lectureRepository.findAllRatingsByLectureIds(lectures.map { it.lecture.id!! }) + .associate { it.id to it.avgRating } + return lectures.map { + EvLectureSummaryForSnutt( + snuttId = it.snuttId!!, + evLectureId = it.lecture.id!!, + avgRating = ratingMap[it.lecture.id!!], + ) + } + } + private fun mappingTagsToLectureProperty(request: SearchLectureRequest): SearchQueryDto { val tags = tagRepository.getTagsWithTagGroupByTagsIdIsIn(request.tags) val tagMap: Map> = tags.groupBy({ it.tagGroup.name }, { diff --git a/core/src/main/resources/db/V4__semester_lecture_snutt_id.sql b/core/src/main/resources/db/V4__semester_lecture_snutt_id.sql new file mode 100644 index 0000000..50a730b --- /dev/null +++ b/core/src/main/resources/db/V4__semester_lecture_snutt_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE semester_lecture ADD COLUMN snutt_id CHAR(24) DEFAULT NULL; +ALTER TABLE semester_lecture ADD INDEX semester_lecture_snutt_id_index(snutt_id);