Skip to content

Commit

Permalink
Coursebooks API migration (#262)
Browse files Browse the repository at this point in the history
  • Loading branch information
SeonghaeJo authored Sep 4, 2024
1 parent be33a07 commit 92593e6
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 12 deletions.
38 changes: 38 additions & 0 deletions api/src/main/kotlin/handler/CoursebookHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.wafflestudio.snu4t.handler

import com.wafflestudio.snu4t.common.enum.Semester
import com.wafflestudio.snu4t.common.util.SugangSnuUrlUtils.parseSyllabusUrl
import com.wafflestudio.snu4t.coursebook.data.CoursebookOfficialResponse
import com.wafflestudio.snu4t.coursebook.data.CoursebookResponse
import com.wafflestudio.snu4t.coursebook.service.CoursebookService
import com.wafflestudio.snu4t.middleware.SnuttRestApiNoAuthMiddleware
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse

@Component
class CoursebookHandler(
private val coursebookService: CoursebookService,
snuttRestApiNoAuthMiddleware: SnuttRestApiNoAuthMiddleware
) : ServiceHandler(
handlerMiddleware = snuttRestApiNoAuthMiddleware
) {
suspend fun getCoursebooks(req: ServerRequest): ServerResponse = handle(req) {
coursebookService.getCoursebooks().map { CoursebookResponse(it) }
}

suspend fun getLatestCoursebook(req: ServerRequest): ServerResponse = handle(req) {
val latestCoursebook = coursebookService.getLatestCoursebook()
CoursebookResponse(latestCoursebook)
}

suspend fun getCoursebookOfficial(req: ServerRequest): ServerResponse = handle(req) {
val url = parseSyllabusUrl(
year = req.parseRequiredQueryParam("year"),
semester = req.parseRequiredQueryParam("semester") { Semester.getOfValue(it.toInt()) },
courseNumber = req.parseRequiredQueryParam("course_number"),
lectureNumber = req.parseRequiredQueryParam("lecture_number")
)
CoursebookOfficialResponse(url)
}
}
13 changes: 13 additions & 0 deletions api/src/main/kotlin/router/MainRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.wafflestudio.snu4t.handler.AuthHandler
import com.wafflestudio.snu4t.handler.BookmarkHandler
import com.wafflestudio.snu4t.handler.BuildingHandler
import com.wafflestudio.snu4t.handler.ConfigHandler
import com.wafflestudio.snu4t.handler.CoursebookHandler
import com.wafflestudio.snu4t.handler.DeviceHandler
import com.wafflestudio.snu4t.handler.EvHandler
import com.wafflestudio.snu4t.handler.FriendHandler
Expand All @@ -23,6 +24,7 @@ import com.wafflestudio.snu4t.router.docs.AuthDocs
import com.wafflestudio.snu4t.router.docs.BookmarkDocs
import com.wafflestudio.snu4t.router.docs.BuildingsDocs
import com.wafflestudio.snu4t.router.docs.ConfigDocs
import com.wafflestudio.snu4t.router.docs.CoursebookDocs
import com.wafflestudio.snu4t.router.docs.EvDocs
import com.wafflestudio.snu4t.router.docs.FriendDocs
import com.wafflestudio.snu4t.router.docs.LectureSearchDocs
Expand Down Expand Up @@ -59,6 +61,7 @@ class MainRouter(
private val adminHandler: AdminHandler,
private val buildingHandler: BuildingHandler,
private val evHandler: EvHandler,
private val coursebookHandler: CoursebookHandler,
private val tagHandler: TagHandler
) {
@Bean
Expand Down Expand Up @@ -257,6 +260,16 @@ class MainRouter(
GET("/ev/lectures/{lectureId}/summary", evHandler::getLectureEvaluationSummary)
}

@Bean
@CoursebookDocs
fun coursebookRouter() = v1CoRouter {
"/course_books".nest {
GET("", coursebookHandler::getCoursebooks)
GET("/recent", coursebookHandler::getLatestCoursebook)
GET("/official", coursebookHandler::getCoursebookOfficial)
}
}

private fun v1CoRouter(r: CoRouterFunctionDsl.() -> Unit) = coRouter {
path("/v1").or("").nest(r)
}
Expand Down
67 changes: 67 additions & 0 deletions api/src/main/kotlin/router/docs/CoursebookDocs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.wafflestudio.snu4t.router.docs

import com.wafflestudio.snu4t.coursebook.data.CoursebookOfficialResponse
import com.wafflestudio.snu4t.coursebook.data.CoursebookResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.enums.ParameterIn
import io.swagger.v3.oas.annotations.media.ArraySchema
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import org.springdoc.core.annotations.RouterOperation
import org.springdoc.core.annotations.RouterOperations
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.RequestMethod

@RouterOperations(
RouterOperation(
path = "/v1/course_books",
method = [RequestMethod.GET],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "getAllCoursebooks",
responses = [
ApiResponse(
responseCode = "200",
content = [Content(array = ArraySchema(schema = Schema(implementation = CoursebookResponse::class)))]
)
]
)
),
RouterOperation(
path = "/v1/course_books/recent",
method = [RequestMethod.GET],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "getMostRecentCoursebook",
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = CoursebookResponse::class))]
)
]
)
),
RouterOperation(
path = "/v1/course_books/official",
method = [RequestMethod.GET],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "getSyllabusUrl",
parameters = [
Parameter(`in` = ParameterIn.QUERY, name = "year", required = true, example = "2024"),
Parameter(`in` = ParameterIn.QUERY, name = "semester", required = true, example = "3"),
Parameter(`in` = ParameterIn.QUERY, name = "course_number", required = true, example = "M1522.001400"),
Parameter(`in` = ParameterIn.QUERY, name = "lecture_number", required = true, example = "001")
],
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = CoursebookOfficialResponse::class))]
)
]
)
)
)
annotation class CoursebookDocs()
6 changes: 3 additions & 3 deletions batch/src/main/kotlin/sugangsnu/common/SugangSnuRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package com.wafflestudio.snu4t.sugangsnu.common
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.wafflestudio.snu4t.common.enum.Semester
import com.wafflestudio.snu4t.common.util.SugangSnuUrlUtils.convertSemesterToSugangSnuSearchString
import com.wafflestudio.snu4t.sugangsnu.common.api.SugangSnuApi
import com.wafflestudio.snu4t.sugangsnu.common.data.SugangSnuCoursebookCondition
import com.wafflestudio.snu4t.sugangsnu.common.data.SugangSnuLectureInfo
import com.wafflestudio.snu4t.sugangsnu.common.utils.toSugangSnuSearchString
import org.springframework.core.io.buffer.PooledDataBuffer
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
Expand Down Expand Up @@ -48,7 +48,7 @@ class SugangSnuRepository(
courseNumber: String,
lectureNumber: String
): SugangSnuLectureInfo = sugangSnuApi.get().uri { builder ->
val semesterSearchString = semester.toSugangSnuSearchString()
val semesterSearchString = convertSemesterToSugangSnuSearchString(semester)
builder.path(SUGANG_SNU_SEARCH_POPUP_PATH)
.query(DEFAULT_SEARCH_POPUP_PARAMS)
.queryParam("openSchyy", year)
Expand Down Expand Up @@ -84,7 +84,7 @@ class SugangSnuRepository(
query(DEFAULT_LECTURE_EXCEL_DOWNLOAD_PARAMS)
queryParam("srchLanguage", language)
queryParam("srchOpenSchyy", year)
queryParam("srchOpenShtm", semester.toSugangSnuSearchString())
queryParam("srchOpenShtm", convertSemesterToSugangSnuSearchString(semester))
build()
}
}.accept(MediaType.TEXT_HTML).awaitExchange {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ import com.wafflestudio.snu4t.coursebook.data.Coursebook
import com.wafflestudio.snu4t.lectures.data.Lecture
import kotlin.reflect.KProperty1

fun Semester.toSugangSnuSearchString(): String {
return when (this) {
Semester.SPRING -> "U000200001U000300001"
Semester.SUMMER -> "U000200001U000300002"
Semester.AUTUMN -> "U000200002U000300001"
Semester.WINTER -> "U000200002U000300002"
}
}

fun KProperty1<Lecture, *>.toKoreanFieldName(): String = when (this) {
Lecture::classification -> "교과 구분"
Lecture::department -> "학부"
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/kotlin/common/enum/Semester.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class SemesterReadConverter : Converter<Int, Semester> {
override fun convert(source: Int): Semester = Semester.getOfValue(source)!!
}

@ReadingConverter
@Component
class SemesterNumberReadConverter : Converter<Number, Semester> {
override fun convert(source: Number): Semester = Semester.getOfValue(source.toInt())!!
}

@Component
@WritingConverter
class SemesterWriteConverter : Converter<Semester, Int> {
Expand Down
42 changes: 42 additions & 0 deletions core/src/main/kotlin/common/util/SugangSnuUrlUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.wafflestudio.snu4t.common.util

import com.wafflestudio.snu4t.common.enum.Semester
import org.springframework.web.util.DefaultUriBuilderFactory

object SugangSnuUrlUtils {

fun convertSemesterToSugangSnuSearchString(semester: Semester): String = when (semester) {
Semester.SPRING -> "U000200001U000300001"
Semester.SUMMER -> "U000200001U000300002"
Semester.AUTUMN -> "U000200002U000300001"
Semester.WINTER -> "U000200002U000300002"
}

fun parseSyllabusUrl(
year: Int,
semester: Semester,
courseNumber: String,
lectureNumber: String
): String = DefaultUriBuilderFactory().builder()
.scheme("http")
.host("sugang.snu.ac.kr")
.path("/sugang/cc/cc103.action")
.queryParam("openSchyy", year)
.queryParam("openShtmFg", makeOpenShtmFg(semester))
.queryParam("openDetaShtmFg", makeOpenDetaShtmFg(semester))
.queryParam("sbjtCd", courseNumber)
.queryParam("ltNo", lectureNumber)
.queryParam("sbjtSubhCd", "000")
.build()
.toString()

private fun makeOpenShtmFg(semester: Semester) = when (semester) {
Semester.SPRING, Semester.SUMMER -> "U000200001"
Semester.AUTUMN, Semester.WINTER -> "U000200002"
}

private fun makeOpenDetaShtmFg(semester: Semester) = when (semester) {
Semester.SPRING, Semester.AUTUMN -> "U000300001"
Semester.SUMMER, Semester.WINTER -> "U000300002"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wafflestudio.snu4t.coursebook.data

data class CoursebookOfficialResponse(
val url: String
)
18 changes: 18 additions & 0 deletions core/src/main/kotlin/coursebook/data/CoursebookResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.wafflestudio.snu4t.coursebook.data

import com.fasterxml.jackson.annotation.JsonProperty
import com.wafflestudio.snu4t.common.enum.Semester
import java.time.Instant

data class CoursebookResponse(
val year: Int,
val semester: Semester,
@JsonProperty("updated_at")
val updatedAt: Instant
) {
constructor(coursebook: Coursebook) : this(
year = coursebook.year,
semester = coursebook.semester,
updatedAt = coursebook.updatedAt
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ import org.springframework.stereotype.Repository
@Repository
interface CoursebookRepository : CoroutineCrudRepository<Coursebook, String> {
suspend fun findFirstByOrderByYearDescSemesterDesc(): Coursebook

suspend fun findAllByOrderByYearDescSemesterDesc(): List<Coursebook>
}
4 changes: 4 additions & 0 deletions core/src/main/kotlin/coursebook/service/CoursebookService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import org.springframework.stereotype.Service

interface CoursebookService {
suspend fun getLatestCoursebook(): Coursebook

suspend fun getCoursebooks(): List<Coursebook>
}

@Service
class CoursebookServiceImpl(private val coursebookRepository: CoursebookRepository) : CoursebookService {
override suspend fun getLatestCoursebook(): Coursebook =
coursebookRepository.findFirstByOrderByYearDescSemesterDesc()

override suspend fun getCoursebooks(): List<Coursebook> = coursebookRepository.findAllByOrderByYearDescSemesterDesc()
}

0 comments on commit 92593e6

Please sign in to comment.