From 0d9b1366f83f4f2afa5394d0ef45841f5465db80 Mon Sep 17 00:00:00 2001 From: jdahl0711 Date: Mon, 11 May 2026 23:50:18 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EB=8B=A4=EC=9D=8C=20=ED=95=99=EA=B8=B0=20l?= =?UTF-8?q?ectures=20sync=20=EC=8B=9C=20semester=20tag=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../snuttev/sync/SnuttLectureSyncJobConfig.kt | 49 +++++++++++++++++++ .../snuttev/core/common/error/ErrorType.kt | 1 + .../core/common/error/SnuttException.kt | 2 + .../snuttev/core/common/model/BaseEntity.kt | 8 +-- .../snuttev/core/common/type/Semester.kt | 18 +++++-- .../domain/tag/repository/TagRepository.kt | 11 +++++ 6 files changed, 81 insertions(+), 8 deletions(-) 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..5926fc54 100644 --- a/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt +++ b/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt @@ -1,18 +1,26 @@ package com.wafflestudio.snuttev.sync +import com.wafflestudio.snuttev.core.common.error.TagGroupNotFoundException import com.wafflestudio.snuttev.core.common.type.LectureClassification +import com.wafflestudio.snuttev.core.common.type.Semester 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.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.model.Tag +import com.wafflestudio.snuttev.core.domain.tag.repository.TagGroupRepository +import com.wafflestudio.snuttev.core.domain.tag.repository.TagRepository 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,6 +45,8 @@ 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, ) { companion object { @@ -84,6 +94,7 @@ class SnuttLectureSyncJobConfig( .and("semester") .isEqualTo(targetSemester), ), + TargetYearSemester(targetYear, targetSemester), ), ).next(ratingSyncJobStep(jobRepository)) .build() @@ -111,6 +122,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 +133,7 @@ class SnuttLectureSyncJobConfig( ).reader(reader(query)) .processor(processor()) .writer(writer()) + .listener(tagSaveStepListener(targetYearSemester)) .build() private fun reader(query: Query): MongoCursorItemReader = @@ -187,6 +200,37 @@ 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) { + saveTagIfNotExists(targetYearSemester) + } + return stepExecution.exitStatus + } + } + } + + private fun saveTagIfNotExists(targetYearSemester: TargetYearSemester) { + val stringValue = targetYearSemester.year.toString() + "," + targetYearSemester.semester.toString() + if (tagRepository.searchTagByStringValue(stringValue) == null) { + val tagGroup = tagGroupRepository.findByName(name = "학기") ?: throw TagGroupNotFoundException + val name = + targetYearSemester.year.toString() + " " + + Semester.labelOf(targetYearSemester.semester) + "학기" + val ordering = -1 + tagRepository.findMinOrderingByTagGroupId(tagGroup.id!!) + tagRepository.save( + Tag( + tagGroup = tagGroup, + name = name, + ordering = ordering, + stringValue = stringValue, + description = null, + ), + ) + } + } + private fun ratingSyncJobStep(jobRepository: JobRepository): Step = StepBuilder(SnuttRatingSyncJobConfig.RATING_SYNC_JOB_NAME, jobRepository) .job(ratingSyncJob) @@ -198,3 +242,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/error/ErrorType.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt index d1231112..e213bf50 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt @@ -11,6 +11,7 @@ enum class ErrorType( WRONG_MAIN_TAG(20002, HttpStatus.BAD_REQUEST), WRONG_SEARCH_TAG(20003, HttpStatus.BAD_REQUEST), EVALUATION_CONTENT_BLANK(20004, HttpStatus.BAD_REQUEST), + INVALID_SEMESTER_VALUE(20005, HttpStatus.BAD_REQUEST), // 401 UNAUTHORIZED(21001, HttpStatus.UNAUTHORIZED), diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt index 611824d9..fcfb80c3 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt @@ -33,3 +33,5 @@ object EvaluationLikeAlreadyNotExistsException : SnuttException(ErrorType.EVALUA object EvaluationContentBlankException : SnuttException(ErrorType.EVALUATION_CONTENT_BLANK) object LectureMismatchException : SnuttException(ErrorType.LECTURE_MISMATCH) + +object InvalidSemesterValueException : SnuttException(ErrorType.INVALID_SEMESTER_VALUE) 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..2007257b 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 @@ -1,10 +1,20 @@ package com.wafflestudio.snuttev.core.common.type +import com.wafflestudio.snuttev.core.common.error.InvalidSemesterValueException + 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 labelOf(value: Int): String = + entries.firstOrNull { it.value == value }?.label + ?: throw InvalidSemesterValueException + } } 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 } From 963c049a218d5cc47089a08af45239604d197f64 Mon Sep 17 00:00:00 2001 From: jdahl0711 Date: Wed, 27 May 2026 18:07:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?INVALID=5FSEMESTER=5FVALUE=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wafflestudio/snuttev/core/common/error/ErrorType.kt | 1 - .../snuttev/core/common/error/SnuttException.kt | 2 -- .../com/wafflestudio/snuttev/core/common/type/Semester.kt | 6 +----- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt index e213bf50..d1231112 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt @@ -11,7 +11,6 @@ enum class ErrorType( WRONG_MAIN_TAG(20002, HttpStatus.BAD_REQUEST), WRONG_SEARCH_TAG(20003, HttpStatus.BAD_REQUEST), EVALUATION_CONTENT_BLANK(20004, HttpStatus.BAD_REQUEST), - INVALID_SEMESTER_VALUE(20005, HttpStatus.BAD_REQUEST), // 401 UNAUTHORIZED(21001, HttpStatus.UNAUTHORIZED), diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt index fcfb80c3..611824d9 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt @@ -33,5 +33,3 @@ object EvaluationLikeAlreadyNotExistsException : SnuttException(ErrorType.EVALUA object EvaluationContentBlankException : SnuttException(ErrorType.EVALUATION_CONTENT_BLANK) object LectureMismatchException : SnuttException(ErrorType.LECTURE_MISMATCH) - -object InvalidSemesterValueException : SnuttException(ErrorType.INVALID_SEMESTER_VALUE) 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 2007257b..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 @@ -1,7 +1,5 @@ package com.wafflestudio.snuttev.core.common.type -import com.wafflestudio.snuttev.core.common.error.InvalidSemesterValueException - enum class Semester( val value: Int, val label: String, @@ -13,8 +11,6 @@ enum class Semester( ; companion object { - fun labelOf(value: Int): String = - entries.firstOrNull { it.value == value }?.label - ?: throw InvalidSemesterValueException + fun labelOfOrNull(value: Int): String? = entries.firstOrNull { it.value == value }?.label } } From 8bd28f7ce08aa5e7911272d311f6dd7386a35173 Mon Sep 17 00:00:00 2001 From: jdahl0711 Date: Wed, 27 May 2026 18:07:34 +0900 Subject: [PATCH 3/3] =?UTF-8?q?semester=20tag=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20tagService=EB=A1=9C=20=EC=9D=B4=EB=8F=99/INVALID=5F?= =?UTF-8?q?SEMESTER=5FVALUE=20=EC=97=90=EB=9F=AC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../snuttev/sync/SnuttLectureSyncJobConfig.kt | 27 ++---------- .../snuttev/core/common/error/ErrorType.kt | 1 - .../core/common/error/SnuttException.kt | 2 - .../snuttev/core/common/type/Semester.kt | 6 +-- .../core/domain/tag/service/TagService.kt | 42 +++++++++++++++++++ 5 files changed, 46 insertions(+), 32 deletions(-) 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 5926fc54..d751b3c7 100644 --- a/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt +++ b/batch/src/main/kotlin/com/wafflestudio/snuttev/sync/SnuttLectureSyncJobConfig.kt @@ -1,17 +1,15 @@ package com.wafflestudio.snuttev.sync -import com.wafflestudio.snuttev.core.common.error.TagGroupNotFoundException import com.wafflestudio.snuttev.core.common.type.LectureClassification -import com.wafflestudio.snuttev.core.common.type.Semester 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.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.model.Tag 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 @@ -48,6 +46,7 @@ class SnuttLectureSyncJobConfig( 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" @@ -204,33 +203,13 @@ class SnuttLectureSyncJobConfig( return object : StepExecutionListener { override fun afterStep(stepExecution: StepExecution): ExitStatus { if (stepExecution.exitStatus == ExitStatus.COMPLETED && targetYearSemester != null) { - saveTagIfNotExists(targetYearSemester) + tagService.saveYearSemesterTagIfNotExists(targetYearSemester.year, targetYearSemester.semester) } return stepExecution.exitStatus } } } - private fun saveTagIfNotExists(targetYearSemester: TargetYearSemester) { - val stringValue = targetYearSemester.year.toString() + "," + targetYearSemester.semester.toString() - if (tagRepository.searchTagByStringValue(stringValue) == null) { - val tagGroup = tagGroupRepository.findByName(name = "학기") ?: throw TagGroupNotFoundException - val name = - targetYearSemester.year.toString() + " " + - Semester.labelOf(targetYearSemester.semester) + "학기" - val ordering = -1 + tagRepository.findMinOrderingByTagGroupId(tagGroup.id!!) - tagRepository.save( - Tag( - tagGroup = tagGroup, - name = name, - ordering = ordering, - stringValue = stringValue, - description = null, - ), - ) - } - } - private fun ratingSyncJobStep(jobRepository: JobRepository): Step = StepBuilder(SnuttRatingSyncJobConfig.RATING_SYNC_JOB_NAME, jobRepository) .job(ratingSyncJob) diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt index e213bf50..d1231112 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/ErrorType.kt @@ -11,7 +11,6 @@ enum class ErrorType( WRONG_MAIN_TAG(20002, HttpStatus.BAD_REQUEST), WRONG_SEARCH_TAG(20003, HttpStatus.BAD_REQUEST), EVALUATION_CONTENT_BLANK(20004, HttpStatus.BAD_REQUEST), - INVALID_SEMESTER_VALUE(20005, HttpStatus.BAD_REQUEST), // 401 UNAUTHORIZED(21001, HttpStatus.UNAUTHORIZED), diff --git a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt index fcfb80c3..611824d9 100644 --- a/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt +++ b/core/src/main/kotlin/com/wafflestudio/snuttev/core/common/error/SnuttException.kt @@ -33,5 +33,3 @@ object EvaluationLikeAlreadyNotExistsException : SnuttException(ErrorType.EVALUA object EvaluationContentBlankException : SnuttException(ErrorType.EVALUATION_CONTENT_BLANK) object LectureMismatchException : SnuttException(ErrorType.LECTURE_MISMATCH) - -object InvalidSemesterValueException : SnuttException(ErrorType.INVALID_SEMESTER_VALUE) 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 2007257b..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 @@ -1,7 +1,5 @@ package com.wafflestudio.snuttev.core.common.type -import com.wafflestudio.snuttev.core.common.error.InvalidSemesterValueException - enum class Semester( val value: Int, val label: String, @@ -13,8 +11,6 @@ enum class Semester( ; companion object { - fun labelOf(value: Int): String = - entries.firstOrNull { it.value == value }?.label - ?: throw InvalidSemesterValueException + fun labelOfOrNull(value: Int): String? = entries.firstOrNull { it.value == value }?.label } } 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, + ), + ) + } + } }