Skip to content

Commit 31df080

Browse files
authored
25학번 이전 교양분류 보여주는 기능 추가 (#325)
1 parent a330efa commit 31df080

File tree

16 files changed

+149
-2
lines changed

16 files changed

+149
-2
lines changed

api/src/main/kotlin/handler/LectureSearchHandler.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ data class SearchQueryLegacy(
4848
val offset: Long = page * 20L,
4949
val limit: Int = 20,
5050
val sortCriteria: String? = null,
51+
val categoryPre2025: List<String>? = null,
5152
) {
5253
fun toSearchDto(): SearchDto {
5354
return SearchDto(
@@ -67,6 +68,7 @@ data class SearchQueryLegacy(
6768
offset = offset,
6869
limit = limit,
6970
sortBy = sortCriteria,
71+
categoryPre2025 = categoryPre2025,
7072
)
7173
}
7274

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.wafflestudio.snu4t.pre2025category.api
2+
3+
import org.springframework.context.annotation.Bean
4+
import org.springframework.context.annotation.Configuration
5+
import org.springframework.http.client.reactive.ReactorClientHttpConnector
6+
import org.springframework.web.reactive.function.client.ExchangeStrategies
7+
import org.springframework.web.reactive.function.client.WebClient
8+
import reactor.netty.http.client.HttpClient
9+
10+
@Configuration
11+
class GoogleDocsApiConfig {
12+
companion object {
13+
const val GOOGLE_DOCS_BASE_URL = "https://docs.google.com"
14+
}
15+
16+
@Bean
17+
fun googleDocsApi(): GoogleDocsApi {
18+
val exchangeStrategies: ExchangeStrategies =
19+
ExchangeStrategies.builder()
20+
.codecs { it.defaultCodecs().maxInMemorySize(-1) } // to unlimited memory size
21+
.build()
22+
23+
val httpClient =
24+
HttpClient.create()
25+
.followRedirect(true)
26+
.compress(true)
27+
28+
return WebClient.builder()
29+
.baseUrl(GOOGLE_DOCS_BASE_URL)
30+
.clientConnector(ReactorClientHttpConnector(httpClient))
31+
.exchangeStrategies(exchangeStrategies)
32+
.build()
33+
.let(::GoogleDocsApi)
34+
}
35+
}
36+
37+
class GoogleDocsApi(webClient: WebClient) : WebClient by webClient
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.wafflestudio.snu4t.pre2025category.repository
2+
3+
import com.wafflestudio.snu4t.pre2025category.api.GoogleDocsApi
4+
import org.springframework.core.io.buffer.PooledDataBuffer
5+
import org.springframework.http.MediaType
6+
import org.springframework.stereotype.Component
7+
import org.springframework.web.reactive.function.client.awaitBody
8+
import org.springframework.web.reactive.function.client.awaitExchange
9+
import org.springframework.web.reactive.function.client.createExceptionAndAwait
10+
11+
@Component
12+
class CategoryPre2025Repository(
13+
private val googleDocsApi: GoogleDocsApi,
14+
) {
15+
companion object {
16+
const val SPREADSHEET_PATH = "/spreadsheets/d"
17+
const val SPREADSHEET_KEY = "/1Ok2gu7rW1VYlKmC_zSjNmcljef0kstm19P9zJ_5s_QA"
18+
}
19+
20+
suspend fun fetchCategoriesPre2025(): PooledDataBuffer =
21+
googleDocsApi.get().uri { builder ->
22+
builder.run {
23+
path(SPREADSHEET_PATH)
24+
path(SPREADSHEET_KEY)
25+
path("/export")
26+
queryParam("format", "xlsx")
27+
build()
28+
}
29+
}.accept(MediaType.TEXT_HTML).awaitExchange {
30+
if (it.statusCode().is2xxSuccessful) {
31+
it.awaitBody()
32+
} else {
33+
throw it.createExceptionAndAwait()
34+
}
35+
}
36+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.wafflestudio.snu4t.pre2025category.service
2+
3+
import com.wafflestudio.snu4t.pre2025category.repository.CategoryPre2025Repository
4+
import org.apache.poi.ss.usermodel.WorkbookFactory
5+
import org.springframework.stereotype.Service
6+
7+
@Service
8+
class CategoryPre2025FetchService(
9+
private val categoryPre2025Repository: CategoryPre2025Repository,
10+
) {
11+
suspend fun getCategoriesPre2025(): Map<String, String> {
12+
val oldCategoriesXlsx = categoryPre2025Repository.fetchCategoriesPre2025()
13+
val workbook = WorkbookFactory.create(oldCategoriesXlsx.asInputStream())
14+
return workbook.sheetIterator().asSequence()
15+
.flatMap { sheet ->
16+
sheet.rowIterator().asSequence()
17+
.drop(3)
18+
.map { row ->
19+
try {
20+
val currentCourseNumber = row.getCell(7).stringCellValue
21+
val oldCategory = row.getCell(1).stringCellValue
22+
if (currentCourseNumber.isBlank() || oldCategory.isBlank()) {
23+
return@map null
24+
}
25+
currentCourseNumber to oldCategory
26+
} catch (e: Exception) {
27+
null
28+
}
29+
}
30+
.filterNotNull()
31+
}
32+
.toMap()
33+
}
34+
}

batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class SugangSnuFetchServiceImpl(
136136
category = "",
137137
classPlaceAndTimes = classTimes,
138138
registrationCount = registrationCount,
139+
categoryPre2025 = null,
139140
)
140141
}
141142
}

batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.wafflestudio.snu4t.lecturebuildings.service.LectureBuildingService
1010
import com.wafflestudio.snu4t.lectures.data.Lecture
1111
import com.wafflestudio.snu4t.lectures.service.LectureService
1212
import com.wafflestudio.snu4t.lectures.utils.ClassTimeUtils
13+
import com.wafflestudio.snu4t.pre2025category.service.CategoryPre2025FetchService
1314
import com.wafflestudio.snu4t.sugangsnu.common.SugangSnuRepository
1415
import com.wafflestudio.snu4t.sugangsnu.common.data.SugangSnuCoursebookCondition
1516
import com.wafflestudio.snu4t.sugangsnu.common.service.SugangSnuFetchService
@@ -46,6 +47,7 @@ interface SugangSnuSyncService {
4647
@Service
4748
class SugangSnuSyncServiceImpl(
4849
private val sugangSnuFetchService: SugangSnuFetchService,
50+
private val categoryPre2025FetchService: CategoryPre2025FetchService,
4951
private val lectureService: LectureService,
5052
private val timeTableRepository: TimetableRepository,
5153
private val sugangSnuRepository: SugangSnuRepository,
@@ -56,7 +58,15 @@ class SugangSnuSyncServiceImpl(
5658
private val cache: Cache,
5759
) : SugangSnuSyncService {
5860
override suspend fun updateCoursebook(coursebook: Coursebook): List<UserLectureSyncResult> {
59-
val newLectures = sugangSnuFetchService.getSugangSnuLectures(coursebook.year, coursebook.semester)
61+
val courseNumberCategoryPre2025Map = categoryPre2025FetchService.getCategoriesPre2025()
62+
val newLectures =
63+
sugangSnuFetchService.getSugangSnuLectures(coursebook.year, coursebook.semester)
64+
.map { lecture ->
65+
if (courseNumberCategoryPre2025Map[lecture.courseNumber] == null || lecture.year < 2025) {
66+
return@map lecture
67+
}
68+
lecture.copy(categoryPre2025 = courseNumberCategoryPre2025Map[lecture.courseNumber])
69+
}
6070
val oldLectures =
6171
lectureService.getLecturesByYearAndSemesterAsFlow(coursebook.year, coursebook.semester).toList()
6272
val compareResult = compareLectures(newLectures, oldLectures)
@@ -71,7 +81,15 @@ class SugangSnuSyncServiceImpl(
7181
}
7282

7383
override suspend fun addCoursebook(coursebook: Coursebook) {
74-
val newLectures = sugangSnuFetchService.getSugangSnuLectures(coursebook.year, coursebook.semester)
84+
val courseNumberCategoryPre2025Map = categoryPre2025FetchService.getCategoriesPre2025()
85+
val newLectures =
86+
sugangSnuFetchService.getSugangSnuLectures(coursebook.year, coursebook.semester)
87+
.map { lecture ->
88+
if (courseNumberCategoryPre2025Map[lecture.courseNumber] == null || lecture.year < 2025) {
89+
return@map lecture
90+
}
91+
lecture.copy(categoryPre2025 = courseNumberCategoryPre2025Map[lecture.courseNumber])
92+
}
7593
lectureService.upsertLectures(newLectures)
7694
syncTagList(coursebook, newLectures)
7795

@@ -125,6 +143,7 @@ class SugangSnuSyncServiceImpl(
125143
credit = acc.credit + lecture.credit,
126144
instructor = acc.instructor + lecture.instructor,
127145
category = acc.category + lecture.category,
146+
categoryPre2025 = acc.categoryPre2025 + lecture.categoryPre2025,
128147
)
129148
}.let { parsedTag ->
130149
TagCollection(
@@ -135,6 +154,7 @@ class SugangSnuSyncServiceImpl(
135154
credit = parsedTag.credit.sorted().map { "${it}학점" },
136155
instructor = parsedTag.instructor.filterNotNull().filter { it.isNotBlank() }.sorted(),
137156
category = parsedTag.category.filterNotNull().filter { it.isNotBlank() }.sorted(),
157+
categoryPre2025 = parsedTag.categoryPre2025.filterNotNull().filter { it.isNotBlank() }.sorted(),
138158
)
139159
}
140160
val tagList =
@@ -347,4 +367,5 @@ data class ParsedTags(
347367
val instructor: Set<String?> = setOf(),
348368
val category: Set<String?> = setOf(),
349369
val etc: Set<String?> = setOf(),
370+
val categoryPre2025: Set<String?> = setOf(),
350371
)

core/src/main/kotlin/lectures/data/Lecture.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ data class Lecture(
3737
var registrationCount: Int = 0,
3838
var wasFull: Boolean = false,
3939
val evInfo: EvInfo? = null,
40+
val categoryPre2025: String?,
4041
) {
4142
infix fun equalsMetadata(other: Lecture): Boolean {
4243
return this === other ||

core/src/main/kotlin/lectures/dto/LectureDto.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ data class LectureDto(
3232
val registrationCount: Int,
3333
val wasFull: Boolean,
3434
val snuttEvLecture: SnuttEvLectureSummaryDto? = null,
35+
val categoryPre2025: String?,
3536
// FIXME: 안드로이드 구버전 대응용 필드 1년 후 2024년에 삭제 (2023/06/26)
3637
@JsonProperty("class_time_mask")
3738
val classTimeMask: List<Int> = emptyList(),
@@ -61,5 +62,6 @@ fun LectureDto(
6162
registrationCount = lecture.registrationCount,
6263
wasFull = lecture.wasFull,
6364
snuttEvLecture = snuttevLecture,
65+
categoryPre2025 = lecture.categoryPre2025,
6466
classTimeMask = ClassTimeUtils.classTimeToBitmask(lecture.classPlaceAndTimes),
6567
)

core/src/main/kotlin/lectures/dto/SearchDto.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ data class SearchDto(
1919
val offset: Long = page * 20L,
2020
val limit: Int = 20,
2121
val sortBy: String?,
22+
val categoryPre2025: List<String>? = null,
2223
)

core/src/main/kotlin/lectures/repository/LectureCustomRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class LectureCustomRepositoryImpl(
8080
),
8181
)
8282
},
83+
searchCondition.categoryPre2025?.takeIf { it.isNotEmpty() }?.let { Lecture::categoryPre2025 inValues it },
8384
*searchCondition.etcTags.orEmpty().map { etcTag ->
8485
when (etcTag) {
8586
"E" -> Lecture::remark regex ".*ⓔ.*"

0 commit comments

Comments
 (0)