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 b4872cbc..d751b3c7 100644 --- a/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt +++ b/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt @@ -7,12 +7,18 @@ import com.wafflestudio.snuttev.core.domain.lecture.model.SnuttLectureIdMap import com.wafflestudio.snuttev.core.domain.lecture.repository.LectureRepository import com.wafflestudio.snuttev.core.domain.lecture.repository.SemesterLectureRepository import com.wafflestudio.snuttev.core.domain.lecture.repository.SnuttLectureIdMapRepository +import com.wafflestudio.snuttev.core.domain.tag.repository.TagGroupRepository +import com.wafflestudio.snuttev.core.domain.tag.repository.TagRepository +import com.wafflestudio.snuttev.core.domain.tag.service.TagService import com.wafflestudio.snuttev.sync.model.SnuttSemesterLecture import jakarta.persistence.EntityManagerFactory +import org.springframework.batch.core.ExitStatus import org.springframework.batch.core.job.Job import org.springframework.batch.core.job.builder.JobBuilder +import org.springframework.batch.core.listener.StepExecutionListener import org.springframework.batch.core.repository.JobRepository import org.springframework.batch.core.step.Step +import org.springframework.batch.core.step.StepExecution import org.springframework.batch.core.step.builder.StepBuilder import org.springframework.batch.infrastructure.item.ItemProcessor import org.springframework.batch.infrastructure.item.ItemWriter @@ -37,7 +43,10 @@ class SnuttLectureSyncJobConfig( private val semesterLectureRepository: SemesterLectureRepository, private val lectureRepository: LectureRepository, private val snuttLectureIdMapRepository: SnuttLectureIdMapRepository, + private val tagRepository: TagRepository, + private val tagGroupRepository: TagGroupRepository, private val ratingSyncJob: Job, + private val tagService: TagService, ) { companion object { private const val JOB_NAME = "SYNC_JOB" @@ -84,6 +93,7 @@ class SnuttLectureSyncJobConfig( .and("semester") .isEqualTo(targetSemester), ), + TargetYearSemester(targetYear, targetSemester), ), ).next(ratingSyncJobStep(jobRepository)) .build() @@ -111,6 +121,7 @@ class SnuttLectureSyncJobConfig( private fun customReaderStep( jobRepository: JobRepository, query: Query, + targetYearSemester: TargetYearSemester? = null, ): Step = StepBuilder(CUSTOM_READER_JOB_STEP, jobRepository) .chunk(CHUNK_SIZE) @@ -121,6 +132,7 @@ class SnuttLectureSyncJobConfig( ).reader(reader(query)) .processor(processor()) .writer(writer()) + .listener(tagSaveStepListener(targetYearSemester)) .build() private fun reader(query: Query): MongoCursorItemReader = @@ -187,6 +199,17 @@ class SnuttLectureSyncJobConfig( snuttLectureIdMapRepository.saveAll(items.map { it.snuttLectureIdMap }) } + private fun tagSaveStepListener(targetYearSemester: TargetYearSemester? = null): StepExecutionListener { + return object : StepExecutionListener { + override fun afterStep(stepExecution: StepExecution): ExitStatus { + if (stepExecution.exitStatus == ExitStatus.COMPLETED && targetYearSemester != null) { + tagService.saveYearSemesterTagIfNotExists(targetYearSemester.year, targetYearSemester.semester) + } + return stepExecution.exitStatus + } + } + } + private fun ratingSyncJobStep(jobRepository: JobRepository): Step = StepBuilder(SnuttRatingSyncJobConfig.RATING_SYNC_JOB_NAME, jobRepository) .job(ratingSyncJob) @@ -198,3 +221,8 @@ data class SyncProcessResult( val semesterLecture: SemesterLecture, val snuttLectureIdMap: SnuttLectureIdMap, ) + +data class TargetYearSemester( + val year: Int, + val semester: Int, +) diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/model/BaseEntity.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/model/BaseEntity.kt index 3d4af5db..9e6e3f08 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/model/BaseEntity.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/model/BaseEntity.kt @@ -13,11 +13,11 @@ import java.time.LocalDateTime open class BaseEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - open val id: Long? = null, - @Column(name = "created_at", nullable = false) - open val createdAt: LocalDateTime = LocalDateTime.now(), + open var id: Long? = null, + @Column(name = "created_at", nullable = false, updatable = false) + open var createdAt: LocalDateTime = LocalDateTime.now(), @field:UpdateTimestamp @Column(name = "updated_at", nullable = false) @OptimisticLock(excluded = true) - open val updatedAt: LocalDateTime? = LocalDateTime.now(), + open var updatedAt: LocalDateTime? = LocalDateTime.now(), ) diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/type/Semester.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/type/Semester.kt index 6ba4817d..f6798c0e 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/type/Semester.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/type/Semester.kt @@ -2,9 +2,15 @@ package com.wafflestudio.snuttev.core.common.type enum class Semester( val value: Int, + val label: String, ) { - SPRING(1), - SUMMER(2), - AUTUMN(3), - WINTER(4), + SPRING(1, "1"), + SUMMER(2, "여름"), + AUTUMN(3, "2"), + WINTER(4, "겨울"), + ; + + companion object { + fun labelOfOrNull(value: Int): String? = entries.firstOrNull { it.value == value }?.label + } } diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/repository/TagRepository.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/repository/TagRepository.kt index 21c98419..4130e160 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/repository/TagRepository.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/repository/TagRepository.kt @@ -7,4 +7,15 @@ import org.springframework.data.jpa.repository.Query interface TagRepository : JpaRepository { @Query("SELECT t FROM Tag t JOIN FETCH t.tagGroup WHERE t.id IN :tagIdList") fun getTagsWithTagGroupByTagsIdIsIn(tagIdList: List): List + + fun searchTagByStringValue(stringValue: String): Tag? + + @Query( + """ + SELECT COALESCE(MIN(t.ordering), 0) + FROM Tag t + WHERE t.tagGroup.id = :tagGroupId + """, + ) + fun findMinOrderingByTagGroupId(tagGroupId: Long): Int } diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/service/TagService.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/service/TagService.kt index 35e5f87b..74584437 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/service/TagService.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/domain/tag/service/TagService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.snuttev.core.domain.tag.service import com.wafflestudio.snuttev.core.common.error.TagGroupNotFoundException +import com.wafflestudio.snuttev.core.common.type.Semester import com.wafflestudio.snuttev.core.common.util.cache.Cache import com.wafflestudio.snuttev.core.common.util.cache.CacheKey import com.wafflestudio.snuttev.core.domain.tag.dto.SearchTagResponse @@ -9,14 +10,21 @@ import com.wafflestudio.snuttev.core.domain.tag.dto.TagGroupDto import com.wafflestudio.snuttev.core.domain.tag.model.Tag import com.wafflestudio.snuttev.core.domain.tag.model.TagGroup import com.wafflestudio.snuttev.core.domain.tag.repository.TagGroupRepository +import com.wafflestudio.snuttev.core.domain.tag.repository.TagRepository +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class TagService internal constructor( private val tagGroupRepository: TagGroupRepository, + private val tagRepository: TagRepository, private val cache: Cache, ) { + companion object { + private val log = LoggerFactory.getLogger(TagService::class.java) + } + @Transactional(readOnly = true) fun getMainTags(): TagGroupDto = cache.withCache(CacheKey.MAIN_TAGS.build()) { @@ -53,4 +61,38 @@ class TagService internal constructor( description = tag.description, ordering = tag.ordering, ) + + @Transactional + fun saveYearSemesterTagIfNotExists( + year: Int, + semester: Int, + ) { + val stringValue = "$year,$semester" + if (tagRepository.searchTagByStringValue(stringValue) == null) { + val tagGroup = + tagGroupRepository.findByName(name = "학기") ?: run { + log.warn("TagGroup not found. name={}", "학기") + return + } + + val semesterName = + Semester.labelOfOrNull(semester) ?: run { + log.warn("Invalid semester value. semester={}", semester) + return + } + + val name = + year.toString() + " " + semesterName + "학기" + val ordering = -1 + tagRepository.findMinOrderingByTagGroupId(tagGroup.id!!) + tagRepository.save( + Tag( + tagGroup = tagGroup, + name = name, + ordering = ordering, + stringValue = stringValue, + description = null, + ), + ) + } + } }