From 4fae82047d939407d476404e13c3152f83398602 Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Thu, 15 Jan 2026 16:11:18 +0900 Subject: [PATCH 1/4] =?UTF-8?q?categoryPre2025.txt=20=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B5=AC=20=EA=B5=90=EC=96=91=EC=98=81=EC=97=AD=20=EC=9D=BD?= =?UTF-8?q?=EC=96=B4=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pre2025category/api/GoogleDocsApi.kt | 42 -- .../repository/CategoryPre2025Repository.kt | 39 -- .../service/CategoryPre2025FetchService.kt | 38 -- .../common/service/SugangSnuFetchService.kt | 17 +- .../native-image/resource-config.json | 3 +- batch/src/main/resources/categoryPre2025.txt | 555 ++++++++++++++++++ 6 files changed, 571 insertions(+), 123 deletions(-) delete mode 100644 batch/src/main/kotlin/pre2025category/api/GoogleDocsApi.kt delete mode 100644 batch/src/main/kotlin/pre2025category/repository/CategoryPre2025Repository.kt delete mode 100644 batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt create mode 100644 batch/src/main/resources/categoryPre2025.txt diff --git a/batch/src/main/kotlin/pre2025category/api/GoogleDocsApi.kt b/batch/src/main/kotlin/pre2025category/api/GoogleDocsApi.kt deleted file mode 100644 index 8523e99a..00000000 --- a/batch/src/main/kotlin/pre2025category/api/GoogleDocsApi.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.wafflestudio.snutt.pre2025category.api - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.http.client.reactive.ReactorClientHttpConnector -import org.springframework.web.reactive.function.client.ExchangeStrategies -import org.springframework.web.reactive.function.client.WebClient -import reactor.netty.http.client.HttpClient - -@Configuration -class GoogleDocsApiConfig { - companion object { - const val GOOGLE_DOCS_BASE_URL = "https://docs.google.com" - } - - @Bean - fun googleDocsApi(): GoogleDocsApi { - val exchangeStrategies: ExchangeStrategies = - ExchangeStrategies - .builder() - .codecs { it.defaultCodecs().maxInMemorySize(-1) } // to unlimited memory size - .build() - - val httpClient = - HttpClient - .create() - .followRedirect(true) - .compress(true) - - return WebClient - .builder() - .baseUrl(GOOGLE_DOCS_BASE_URL) - .clientConnector(ReactorClientHttpConnector(httpClient)) - .exchangeStrategies(exchangeStrategies) - .build() - .let(::GoogleDocsApi) - } -} - -class GoogleDocsApi( - webClient: WebClient, -) : WebClient by webClient diff --git a/batch/src/main/kotlin/pre2025category/repository/CategoryPre2025Repository.kt b/batch/src/main/kotlin/pre2025category/repository/CategoryPre2025Repository.kt deleted file mode 100644 index 43586764..00000000 --- a/batch/src/main/kotlin/pre2025category/repository/CategoryPre2025Repository.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.wafflestudio.snutt.pre2025category.repository - -import com.wafflestudio.snutt.pre2025category.api.GoogleDocsApi -import org.springframework.core.io.buffer.PooledDataBuffer -import org.springframework.http.MediaType -import org.springframework.stereotype.Component -import org.springframework.web.reactive.function.client.awaitBody -import org.springframework.web.reactive.function.client.awaitExchange -import org.springframework.web.reactive.function.client.createExceptionAndAwait - -@Component -class CategoryPre2025Repository( - private val googleDocsApi: GoogleDocsApi, -) { - companion object { - const val SPREADSHEET_PATH = "/spreadsheets/d" - const val SPREADSHEET_KEY = "/1Ok2gu7rW1VYlKmC_zSjNmcljef0kstm19P9zJ_5s_QA" - } - - suspend fun fetchCategoriesPre2025(): PooledDataBuffer = - googleDocsApi - .get() - .uri { builder -> - builder.run { - path(SPREADSHEET_PATH) - path(SPREADSHEET_KEY) - path("/export") - queryParam("format", "xlsx") - build() - } - }.accept(MediaType.TEXT_HTML) - .awaitExchange { - if (it.statusCode().is2xxSuccessful) { - it.awaitBody() - } else { - throw it.createExceptionAndAwait() - } - } -} diff --git a/batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt b/batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt deleted file mode 100644 index 76fa073c..00000000 --- a/batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.wafflestudio.snutt.pre2025category.service - -import com.wafflestudio.snutt.pre2025category.repository.CategoryPre2025Repository -import org.apache.poi.ss.usermodel.WorkbookFactory -import org.springframework.core.io.buffer.PooledDataBuffer -import org.springframework.stereotype.Service - -@Service -class CategoryPre2025FetchService( - private val categoryPre2025Repository: CategoryPre2025Repository, -) { - suspend fun getCategoriesPre2025(): Map { - val oldCategoriesXlsx: PooledDataBuffer = categoryPre2025Repository.fetchCategoriesPre2025() - - try { - val workbook = WorkbookFactory.create(oldCategoriesXlsx.asInputStream()) - return workbook - .sheetIterator() - .asSequence() - .flatMap { sheet -> - sheet - .rowIterator() - .asSequence() - .drop(4) - .mapNotNull { row -> - runCatching { - val currentCourseNumber = row.getCell(7).stringCellValue - val oldCategory = row.getCell(1).stringCellValue - check(currentCourseNumber.isNotBlank() && oldCategory.isNotBlank()) - currentCourseNumber to oldCategory - }.getOrNull() - } - }.toMap() - } finally { - oldCategoriesXlsx.release() - } - } -} diff --git a/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt b/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt index 348e404c..e63971ce 100644 --- a/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt +++ b/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt @@ -2,12 +2,12 @@ package com.wafflestudio.snutt.sugangsnu.common.service import com.wafflestudio.snutt.common.enums.Semester import com.wafflestudio.snutt.lectures.data.Lecture -import com.wafflestudio.snutt.pre2025category.service.CategoryPre2025FetchService import com.wafflestudio.snutt.sugangsnu.common.SugangSnuRepository import com.wafflestudio.snutt.sugangsnu.common.utils.SugangSnuClassTimeUtils import org.apache.poi.hssf.usermodel.HSSFWorkbook import org.apache.poi.ss.usermodel.Cell import org.slf4j.LoggerFactory +import org.springframework.core.io.ResourceLoader import org.springframework.stereotype.Service interface SugangSnuFetchService { @@ -20,16 +20,27 @@ interface SugangSnuFetchService { @Service class SugangSnuFetchServiceImpl( private val sugangSnuRepository: SugangSnuRepository, - private val categoryPre2025FetchService: CategoryPre2025FetchService, + private val resourceLoader: ResourceLoader, ) : SugangSnuFetchService { private val log = LoggerFactory.getLogger(javaClass) private val quotaRegex = """(?\d+)(\s*\((?\d+)\))?""".toRegex() + private val courseNumberCategoryPre2025Map: Map by lazy { + resourceLoader + .getResource("classpath:categoryPre2025.txt") + .inputStream + .bufferedReader() + .lineSequence() + .filter { it.contains(":") } + .associate { line -> + val (courseNumber, category) = line.split(":", limit = 2) + courseNumber to category + } + } override suspend fun getSugangSnuLectures( year: Int, semester: Semester, ): List { - val courseNumberCategoryPre2025Map = categoryPre2025FetchService.getCategoriesPre2025() val koreanLectureXlsx = sugangSnuRepository.getSugangSnuLectures(year, semester, "ko") val englishLectureXlsx = sugangSnuRepository.getSugangSnuLectures(year, semester, "en") val koreanSheet = HSSFWorkbook(koreanLectureXlsx.asInputStream()).getSheetAt(0) diff --git a/batch/src/main/resources/META-INF/native-image/resource-config.json b/batch/src/main/resources/META-INF/native-image/resource-config.json index df875fcb..4c287310 100644 --- a/batch/src/main/resources/META-INF/native-image/resource-config.json +++ b/batch/src/main/resources/META-INF/native-image/resource-config.json @@ -2,7 +2,8 @@ "resources": { "includes": [ { "pattern": "org/apache/xmlbeans/.*\\.properties" }, - { "pattern": "org/apache/.*\\.xsb" } + { "pattern": "org/apache/.*\\.xsb" }, + { "pattern": "categoryPre2025.txt" } ] } } diff --git a/batch/src/main/resources/categoryPre2025.txt b/batch/src/main/resources/categoryPre2025.txt new file mode 100644 index 00000000..cc76a8de --- /dev/null +++ b/batch/src/main/resources/categoryPre2025.txt @@ -0,0 +1,555 @@ +F11.101:사고와 표현 +F11.201:사고와 표현 +F11.202:사고와 표현 +F11.203:사고와 표현 +F12.101:사고와 표현 +F12.102:사고와 표현 +F12.103:사고와 표현 +F21.100:외국어 +F21.101:외국어 +F21.201:외국어 +F21.202:외국어 +F21.301:외국어 +F21.302:외국어 +F21.303:외국어 +F21.304:외국어 +F21.305:외국어 +F21.306:외국어 +F21.307:외국어 +F22.101:외국어 +F22.102:외국어 +F22.201:외국어 +F22.202:외국어 +F22.301:외국어 +F22.302:외국어 +F22.303:외국어 +F23.101:외국어 +F23.102:외국어 +F23.201:외국어 +F23.202:외국어 +F23.301:외국어 +F23.302:외국어 +F23.303:외국어 +F24.101:외국어 +F24.102:외국어 +F24.201:외국어 +F24.202:외국어 +F24.301:외국어 +F24.302:외국어 +F24.303:외국어 +F25.101:외국어 +F25.102:외국어 +F25.201:외국어 +F25.202:외국어 +F25.301:외국어 +F25.302:외국어 +F25.303:외국어 +F26.101:외국어 +F26.102:외국어 +F26.201:외국어 +F26.202:외국어 +F26.301:외국어 +F26.302:외국어 +F26.303:외국어 +F27.101:외국어 +F27.201:외국어 +F27.202:외국어 +F27.301:외국어 +F28.101:외국어 +F28.102:외국어 +F28.201:외국어 +F28.301:외국어 +F28.302:외국어 +F29.101:외국어 +F29.102:외국어 +F29.103:외국어 +F29.104:외국어 +F29.105:외국어 +F29.106:외국어 +F29.107:외국어 +F29.108:외국어 +F29.109:외국어 +F29.110:외국어 +F29.111:외국어 +F29.112:외국어 +F29.113:외국어 +F29.114:외국어 +F29.115:외국어 +F29.116:외국어 +F29.117:외국어 +F29.118:외국어 +F29.119:외국어 +F29.120:외국어 +F29.121:외국어 +F29.122:외국어 +F29.123:외국어 +F29.124:외국어 +F29.125:외국어 +F29.126:외국어 +F29.127:외국어 +F31.101:수량적 분석과 추론 +F31.102:수량적 분석과 추론 +F31.103:수량적 분석과 추론 +F31.104:수량적 분석과 추론 +F31.105:수량적 분석과 추론 +F31.104L:수량적 분석과 추론 +F31.105L:수량적 분석과 추론 +F31.106:수량적 분석과 추론 +F31.107:수량적 분석과 추론 +F31.106L:수량적 분석과 추론 +F31.107L:수량적 분석과 추론 +F31.108:수량적 분석과 추론 +F31.109:수량적 분석과 추론 +F31.110:수량적 분석과 추론 +F31.109L:수량적 분석과 추론 +F31.110L:수량적 분석과 추론 +F31.111:수량적 분석과 추론 +F31.112:수량적 분석과 추론 +F31.113:수량적 분석과 추론 +F31.114:수량적 분석과 추론 +F31.115:수량적 분석과 추론 +F31.201:수량적 분석과 추론 +F31.202:수량적 분석과 추론 +F32.101:수량적 분석과 추론 +F32.102:수량적 분석과 추론 +F32.102L:수량적 분석과 추론 +F32.103:수량적 분석과 추론 +F32.103L:수량적 분석과 추론 +F33.101:과학적 사고와 실험 +F33.102:과학적 사고와 실험 +F33.103:과학적 사고와 실험 +F33.104:과학적 사고와 실험 +F33.105:과학적 사고와 실험 +F33.106:과학적 사고와 실험 +F33.107:과학적 사고와 실험 +F33.108:과학적 사고와 실험 +F33.109:과학적 사고와 실험 +F33.105L:과학적 사고와 실험 +F33.106L:과학적 사고와 실험 +F33.107L:과학적 사고와 실험 +F33.110:과학적 사고와 실험 +F33.111:과학적 사고와 실험 +F33.111L:과학적 사고와 실험 +F34.101:과학적 사고와 실험 +F34.102:과학적 사고와 실험 +F34.103:과학적 사고와 실험 +F34.104:과학적 사고와 실험 +F34.105:과학적 사고와 실험 +F34.106:과학적 사고와 실험 +F34.103L:과학적 사고와 실험 +F34.104L:과학적 사고와 실험 +F34.105L:과학적 사고와 실험 +F35.101:과학적 사고와 실험 +F35.102:과학적 사고와 실험 +F35.103:과학적 사고와 실험 +F35.104:과학적 사고와 실험 +F35.105:과학적 사고와 실험 +F35.103L:과학적 사고와 실험 +F35.104L:과학적 사고와 실험 +F35.105L:과학적 사고와 실험 +F35.106:과학적 사고와 실험 +F36.101:과학적 사고와 실험 +F36.101L:과학적 사고와 실험 +F36.102:과학적 사고와 실험 +F36.102L:과학적 사고와 실험 +F36.103:과학적 사고와 실험 +F36.103L:과학적 사고와 실험 +F36.104:과학적 사고와 실험 +F36.104L:과학적 사고와 실험 +F36.105:과학적 사고와 실험 +F36.105L:과학적 사고와 실험 +F37.101:컴퓨터와 정보 활용 +F37.201:컴퓨터와 정보 활용 +F37.202:컴퓨터와 정보 활용 +F37.203:컴퓨터와 정보 활용 +F37.204:컴퓨터와 정보 활용 +F37.301:컴퓨터와 정보 활용 +F37.302:컴퓨터와 정보 활용 +F37.303:컴퓨터와 정보 활용 +F37.304:컴퓨터와 정보 활용 +C10.101:언어와 문학 +C10.102:문화와 예술 +C10.103:언어와 문학 +C10.104:언어와 문학 +C10.105:역사와 철학 +C10.106:문화와 예술 +C10.107:언어와 문학 +C10.108:역사와 철학 +C10.109:언어와 문학 +C10.110:언어와 문학 +C10.111:문화와 예술 +C10.112:역사와 철학 +C10.113:언어와 문학 +C10.114:문화와 예술 +C10.115:언어와 문학 +C10.116:문화와 예술 +C10.117:언어와 문학 +C10.118:언어와 문학 +C10.119:언어와 문학 +C10.120:언어와 문학 +C10.121:언어와 문학 +C10.122:언어와 문학 +C10.124:문화와 예술 +C10.125:언어와 문학 +C10.126:문화와 예술 +C10.127:문화와 예술 +C10.128:언어와 문학 +C10.129:역사와 철학 +C10.130:문화와 예술 +C10.131:역사와 철학 +C10.132:언어와 문학 +C10.133:문화와 예술 +C10.134:언어와 문학 +C10.135:문화와 예술 +C10.136:역사와 철학 +C10.137:언어와 문학 +C10.138:인간과 사회 +C10.139:언어와 문학 +C10.140:언어와 문학 +C10.141:문화와 예술 +C10.142:문화와 예술 +C10.143:언어와 문학 +C10.144:문화와 예술 +C10.145:문화와 예술 +C10.146:문화와 예술 +C10.147:문화와 예술 +C10.148:언어와 문학 +C10.149:인간과 사회 +C10.150:역사와 철학 +C10.151:인간과 사회 +C10.152:문화와 예술 +C10.153:문화와 예술 +C10.154:언어와 문학 +C10.155:문화와 예술 +C10.156:언어와 문학 +C10.157:문화와 예술 +C10.158:언어와 문학 +C10.159:언어와 문학 +C10.160:언어와 문학 +C10.161:언어와 문학 +C10.162:역사와 철학 +C10.163:언어와 문학 +C10.164:문화와 예술 +C10.165:언어와 문학 +C10.166:언어와 문학 +C10.167:언어와 문학 +C10.168:언어와 문학 +C10.169:문화와 예술 +C10.170:문화와 예술 +C10.171:문화와 예술 +C10.123:언어와 문학 +C20.101:역사와 철학 +C20.102:역사와 철학 +C20.103:역사와 철학 +C20.104:역사와 철학 +C20.105:역사와 철학 +C20.106:역사와 철학 +C20.107:역사와 철학 +C20.108:역사와 철학 +C20.109:역사와 철학 +C20.110:역사와 철학 +C20.111:문화와 예술 +C20.112:역사와 철학 +C20.113:역사와 철학 +C20.114:역사와 철학 +C20.115:역사와 철학 +C20.116:역사와 철학 +C20.117:역사와 철학 +C20.118:역사와 철학 +C20.119:역사와 철학 +C20.120:역사와 철학 +C20.121:역사와 철학 +C20.122:역사와 철학 +C20.123:역사와 철학 +C20.124:역사와 철학 +C20.125:역사와 철학 +C20.126:역사와 철학 +C20.127:역사와 철학 +C20.128:문화와 예술 +C20.129:문화와 예술 +C20.130:역사와 철학 +C20.131:역사와 철학 +C20.132:역사와 철학 +C20.133:역사와 철학 +C20.134:역사와 철학 +C20.135:역사와 철학 +C20.136:역사와 철학 +C20.137:역사와 철학 +C20.138:역사와 철학 +C20.139:역사와 철학 +C20.140:역사와 철학 +C20.141:역사와 철학 +C20.142:역사와 철학 +C20.143:역사와 철학 +C20.144:문화와 예술 +C20.145:역사와 철학 +C20.146:역사와 철학 +C20.147:역사와 철학 +C20.148:역사와 철학 +C20.149:역사와 철학 +C20.150:역사와 철학 +C20.151:역사와 철학 +C20.152:역사와 철학 +C20.153:역사와 철학 +C20.154:역사와 철학 +C20.155:역사와 철학 +C30.101:정치와 경제 +C30.102:정치와 경제 +C30.103:인간과 사회 +C30.104:정치와 경제 +C30.105:정치와 경제 +C30.106:정치와 경제 +C30.107:정치와 경제 +C30.108:정치와 경제 +C30.109:문화와 예술 +C30.110:인간과 사회 +C30.111:언어와 문학 +C30.112:문화와 예술 +C30.113:인간과 사회 +C30.114:정치와 경제 +C30.115:정치와 경제 +C30.116:정치와 경제 +C30.117:인간과 사회 +C30.118:인간과 사회 +C30.119:인간과 사회 +C30.120:인간과 사회 +C30.121:인간과 사회 +C30.122:역사와 철학 +C30.123:인간과 사회 +C30.124:정치와 경제 +C30.126:언어와 문학 +C30.127:정치와 경제 +C30.128:정치와 경제 +C30.129:인간과 사회 +C30.130:인간과 사회 +C30.132:정치와 경제 +C30.133:정치와 경제 +C30.134:인간과 사회 +C30.135:정치와 경제 +C30.136:정치와 경제 +C30.137:정치와 경제 +C30.138:정치와 경제 +C30.139:인간과 사회 +C30.140:정치와 경제 +C30.141:언어와 문학 +C30.142:정치와 경제 +C30.143:인간과 사회 +C30.144:정치와 경제 +C30.145:인간과 사회 +C30.146:인간과 사회 +C40.101:인간과 사회 +C40.102:역사와 철학 +C40.103:역사와 철학 +C40.104:자연과 기술 +C40.105:언어와 문학 +C40.106:자연과 기술 +C40.107:생명과 환경 +C40.108:자연과 기술 +C40.109:자연과 기술 +C40.110:생명과 환경 +C40.112:생명과 환경 +C40.113:자연과 기술 +C40.114:인간과 사회 +C40.116:생명과 환경 +C40.117:생명과 환경 +C40.118:자연과 기술 +C40.119:자연과 기술 +C40.120:자연과 기술 +C40.121:생명과 환경 +C40.122:자연과 기술 +C40.123:자연과 기술 +C40.124:자연과 기술 +C40.125:생명과 환경 +C40.126:역사와 철학 +C40.127:생명과 환경 +C40.128:자연과 기술 +C40.129:자연과 기술 +C40.130:인간과 사회 +C40.131:자연과 기술 +C40.132:생명과 환경 +C40.133:자연과 기술 +C40.134:자연과 기술 +C40.136:창의와 융합 +E11.101:자연과 기술 +E11.102:언어와 문학 +E11.103:정치와 경제 +E11.105:생명과 환경 +E11.106:창의와 융합 +E11.107:창의와 융합 +E11.108:창의와 융합 +E11.109:창의와 융합 +E11.110:창의와 융합 +E11.111:인간과 사회 +E11.112:대학과 리더십 +E11.114:생명과 환경 +E11.115:창의와 융합 +E11.116:역사와 철학 +E11.117:문화와 예술 +E11.118:역사와 철학 +E11.119:역사와 철학 +E11.120:자연과 기술 +E11.121:자연과 기술 +E11.122:인간과 사회 +E11.123:창의와 융합 +E11.124:문화와 예술 +E11.125:역사와 철학 +E11.126:정치와 경제 +E11.128:정치와 경제 +E11.129:역사와 철학 +E11.130:생명과 환경 +E11.131:창의와 융합 +E11.132:생명과 환경 +E11.133:창의와 융합 +E11.135:역사와 철학 +E11.136:창의와 융합 +E11.137:생명과 환경 +E11.138:문화와 예술 +E11.139:문화와 예술 +E11.140:역사와 철학 +E11.141:창의와 융합 +E11.142:정치와 경제 +E11.143:정치와 경제 +E11.144:창의와 융합 +E11.145:역사와 철학 +E11.146:인간과 사회 +E11.148:언어와 문학 +E11.150:정치와 경제 +E11.151:생명과 환경 +E11.153:창의와 융합 +E11.154:창의와 융합 +E11.155:창의와 융합 +E11.156:창의와 융합 +E11.157:창의와 융합 +E11.158:창의와 융합 +E11.159:창의와 융합 +E11.160:문화와 예술 +E11.161:문화와 예술 +E11.162:역사와 철학 +E11.163:인간과 사회 +E11.164:생명과 환경 +E11.165:정치와 경제 +E11.166:인간과 사회 +E11.167:창의와 융합 +E11.169:자연과 기술 +E11.170:인간과 사회 +E11.171:정치와 경제 +E11.172:문화와 예술 +E11.173:역사와 철학 +E11.174:생명과 환경 +E11.175:대학과 리더십 +E11.176:인간과 사회 +E11.177:생명과 환경 +E11.178:창의와 융합 +E11.181:창의와 융합 +E11.182:창의와 융합 +E11.183:창의와 융합 +E11.184:문화와 예술 +E11.185:인간과 사회 +E11.186:정치와 경제 +E11.187:창의와 융합 +E11.188:문화와 예술 +E11.189:문화와 예술 +E11.190:생명과 환경 +E11.191:생명과 환경 +E11.192:언어와 문학 +E11.193:정치와 경제 +E11.194:자연과 기술 +E11.195:생명과 환경 +E11.104:창의와 융합 +E11.134:창의와 융합 +E11.147:창의와 융합 +E11.149:창의와 융합 +E11.152:창의와 융합 +E11.168:창의와 융합 +E11.180:창의와 융합 +E12.101:한국의 이해 +E12.102:한국의 이해 +E12.103:한국의 이해 +E12.104:한국의 이해 +E12.105:한국의 이해 +E12.106:한국의 이해 +E12.107:한국의 이해 +E12.108:한국의 이해 +E12.109:한국의 이해 +E12.110:한국의 이해 +E12.111:한국의 이해 +E12.112:한국의 이해 +E12.113:한국의 이해 +E20.101:대학과 리더십 +E20.102:대학과 리더십 +E20.103:대학과 리더십 +E20.104:대학과 리더십 +E20.105:대학과 리더십 +E20.106:대학과 리더십 +E20.107:대학과 리더십 +E31.101:창의와 융합 +E31.102:창의와 융합 +E31.103:창의와 융합 +E31.104:창의와 융합 +E31.105:창의와 융합 +E32.101:창의와 융합 +E32.102:창의와 융합 +E41.101:예술 실기 +E41.102:예술 실기 +E41.103:예술 실기 +E41.104:예술 실기 +E41.105:예술 실기 +E42.101:예술 실기 +E42.102:예술 실기 +E42.103:예술 실기 +E42.104:예술 실기 +E42.105:예술 실기 +E42.106:예술 실기 +E42.107:예술 실기 +E43.101:체육 +E43.102:체육 +E43.103:체육 +E43.104:체육 +E43.105:체육 +E43.106:체육 +E43.107:체육 +E43.108:체육 +E43.109:체육 +E43.110:체육 +E43.111:체육 +E43.112:체육 +E43.113:체육 +E43.114:체육 +E43.115:체육 +E43.116:체육 +E43.117:체육 +E43.118:체육 +E43.119:체육 +E43.120:체육 +E43.121:체육 +E43.122:체육 +E43.123:체육 +E43.124:체육 +E43.125:체육 +E43.126:체육 +E43.127:체육 +E43.128:체육 +E43.129:체육 +E43.130:체육 +E43.131:체육 +E51.102:대학과 리더십 +E52.101:대학과 리더십 +E52.102:대학과 리더십 +E52.103:창의와 융합 +E52.104:창의와 융합 +V10.101:창의와 융합 +V10.102:창의와 융합 +V10.103:창의와 융합 +V10.104:창의와 융합 +V10.105:창의와 융합 +V30.101:창의와 융합 +V30.102:창의와 융합 +V30.103:창의와 융합 +V30.104:창의와 융합 +V30.105:창의와 융합 +V30.106:창의와 융합 +V30.107:창의와 융합 +V30.108:창의와 융합 +V30.109:창의와 융합 +V30.110:창의와 융합 +V30.111:창의와 융합 From a036d6cf352ecacde61da28b3a0bc5a59472dffa Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Thu, 15 Jan 2026 19:30:51 +0900 Subject: [PATCH 2/4] =?UTF-8?q?evInfo=EB=A5=BC=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=8B=9C=EC=97=90?= =?UTF-8?q?=EB=8F=84=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sugangsnu/job/sync/service/SugangSnuSyncService.kt | 7 +++++-- core/src/main/kotlin/lectures/data/Lecture.kt | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt b/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt index 055acf3d..0e8c377b 100644 --- a/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt +++ b/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt @@ -106,7 +106,7 @@ class SugangSnuSyncServiceImpl( old, new, Lecture::class.memberProperties.filter { - it != Lecture::id && it.get(old) != it.get(new) + it != Lecture::id && it != Lecture::evInfo && it.get(old) != it.get(new) }, ) } @@ -184,7 +184,10 @@ class SugangSnuSyncServiceImpl( private suspend fun syncLectures(compareResult: SugangSnuLectureCompareResult) { val updatedLectures = compareResult.updatedLectureList.map { diff -> - diff.newData.apply { id = diff.oldData.id } + diff.newData.apply { + id = diff.oldData.id + evInfo = diff.oldData.evInfo + } } lectureService.upsertLectures(compareResult.createdLectureList) diff --git a/core/src/main/kotlin/lectures/data/Lecture.kt b/core/src/main/kotlin/lectures/data/Lecture.kt index 771fc2f8..e505ec9b 100644 --- a/core/src/main/kotlin/lectures/data/Lecture.kt +++ b/core/src/main/kotlin/lectures/data/Lecture.kt @@ -36,7 +36,7 @@ data class Lecture( var courseTitle: String, var registrationCount: Int = 0, var wasFull: Boolean = false, - val evInfo: EvInfo? = null, + var evInfo: EvInfo? = null, var categoryPre2025: String?, ) { infix fun equalsMetadata(other: Lecture): Boolean = From b170e9ff2b446cdc5d5fb1562ee1c62cb9b96a9a Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Sun, 4 Jan 2026 20:26:45 +0900 Subject: [PATCH 3/4] implement Comparable for Coursebook --- core/src/main/kotlin/coursebook/data/Coursebook.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/coursebook/data/Coursebook.kt b/core/src/main/kotlin/coursebook/data/Coursebook.kt index 1dc00546..54e08fda 100644 --- a/core/src/main/kotlin/coursebook/data/Coursebook.kt +++ b/core/src/main/kotlin/coursebook/data/Coursebook.kt @@ -18,4 +18,9 @@ class Coursebook( val semester: Semester, @Field("updated_at") var updatedAt: Instant = Instant.now(), -) +) : Comparable { + override fun compareTo(other: Coursebook): Int = + compareBy { it.year } + .thenBy { it.semester } + .compare(this, other) +} From 2e90adc358db07b17acacef29e59d11b71e0772f Mon Sep 17 00:00:00 2001 From: Chanyeong Lim Date: Fri, 16 Jan 2026 17:04:09 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=EC=B5=9C=EC=8B=A0=20coursebook=EC=9D=B4=20?= =?UTF-8?q?=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=EB=B3=B4=EB=8B=A4=20=EB=8D=94=20=EC=B5=9C=EA=B7=BC?= =?UTF-8?q?=EC=9D=B4=EB=A9=B4=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../job/sync/service/SugangSnuSyncService.kt | 15 +++++++++++++-- .../src/main/kotlin/common/exception/ErrorType.kt | 6 ++++++ .../kotlin/common/exception/SnuttException.kt | 2 ++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt b/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt index 0e8c377b..a05b5872 100644 --- a/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt +++ b/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.snutt.sugangsnu.job.sync.service import com.wafflestudio.snutt.bookmark.repository.BookmarkRepository +import com.wafflestudio.snutt.common.exception.CoursebookRecentThanSugangSnuException import com.wafflestudio.snutt.coursebook.data.Coursebook import com.wafflestudio.snutt.coursebook.repository.CoursebookRepository import com.wafflestudio.snutt.lecturebuildings.data.Campus @@ -376,8 +377,18 @@ class SugangSnuSyncServiceImpl( lectureBuildingService.updateLectureBuildings(updatedPlaceInfos) } - private fun Coursebook.isSyncedToSugangSnu(sugangSnuCoursebookCondition: SugangSnuCoursebookCondition): Boolean = - this.year == sugangSnuCoursebookCondition.latestYear && this.semester == sugangSnuCoursebookCondition.latestSemester + private fun Coursebook.isSyncedToSugangSnu(sugangSnuCoursebookCondition: SugangSnuCoursebookCondition): Boolean { + val sugangSnuCoursebook = + Coursebook( + year = sugangSnuCoursebookCondition.latestYear, + semester = sugangSnuCoursebookCondition.latestSemester, + ) + return when { + sugangSnuCoursebook > this -> false + sugangSnuCoursebook < this -> throw CoursebookRecentThanSugangSnuException + else -> true + } + } } data class ParsedTags( diff --git a/core/src/main/kotlin/common/exception/ErrorType.kt b/core/src/main/kotlin/common/exception/ErrorType.kt index 6592496c..a0c88d6a 100644 --- a/core/src/main/kotlin/common/exception/ErrorType.kt +++ b/core/src/main/kotlin/common/exception/ErrorType.kt @@ -101,4 +101,10 @@ enum class ErrorType( CANNOT_REMOVE_LAST_AUTH_PROVIDER(HttpStatus.CONFLICT, 40909, "최소 한 개의 로그인 수단은 유지해야 합니다", "최소 한 개의 로그인 수단은 유지해야 합니다"), DYNAMIC_LINK_GENERATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, 50001, "링크 생성 실패", "링크 생성에 실패했습니다. 잠시 후 다시 시도해주세요"), + COURSEBOOK_RECENT_THAN_SUGANGSNU( + HttpStatus.INTERNAL_SERVER_ERROR, + 50002, + "현재 Coursebook이 수강신청 사이트보다 최근입니다.", + "현재 Coursebook이 수강신청 사이트보다 최근입니다.", + ), } diff --git a/core/src/main/kotlin/common/exception/SnuttException.kt b/core/src/main/kotlin/common/exception/SnuttException.kt index 855d1813..f4bb05b2 100644 --- a/core/src/main/kotlin/common/exception/SnuttException.kt +++ b/core/src/main/kotlin/common/exception/SnuttException.kt @@ -181,3 +181,5 @@ object DuplicateSocialAccountException : SnuttException(ErrorType.DUPLICATE_SOCI object CannotRemoveLastAuthProviderException : SnuttException(ErrorType.CANNOT_REMOVE_LAST_AUTH_PROVIDER) object DynamicLinkGenerationFailedException : SnuttException(ErrorType.DYNAMIC_LINK_GENERATION_FAILED) + +object CoursebookRecentThanSugangSnuException : SnuttException(ErrorType.COURSEBOOK_RECENT_THAN_SUGANGSNU)