Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
dc9d2e7
add course.extract and course.kt
LOV-ING-U Oct 11, 2025
d1c617c
add course.extract and course.kt
LOV-ING-U Oct 11, 2025
4a07da3
add sql of course, course time, timetable, timetable course
LOV-ING-U Oct 12, 2025
6dab4d8
fix typo of V7
LOV-ING-U Oct 12, 2025
a7aee53
fix typo
LOV-ING-U Oct 12, 2025
3381585
add courseTimeExtract API
LOV-ING-U Oct 12, 2025
e296c75
Merge pull request #1 from LOV-ING-U/KiHwan
subir-sh Oct 20, 2025
10d8d9d
Separate DBs
subir-sh Oct 29, 2025
00b08ec
Make timetable folder
subir-sh Oct 29, 2025
de17e24
Merge pull request #2 from LOV-ING-U/lsh
subir-sh Oct 29, 2025
388b400
swagger 의존성 추가
lee-seulchan Oct 29, 2025
a8f1afd
Create Timetable
subir-sh Oct 29, 2025
db3fa4a
Timetable 구조 만들기
subir-sh Oct 29, 2025
10e6380
add time extract logic
LOV-ING-U Oct 29, 2025
c40182d
webclient add
LOV-ING-U Oct 29, 2025
8dcab9b
fix typo
LOV-ING-U Oct 29, 2025
9ba675d
add API
LOV-ING-U Oct 30, 2025
c91b9b7
fix regex and test logic
LOV-ING-U Oct 30, 2025
f3b826e
finish API
LOV-ING-U Oct 30, 2025
6e033d2
need to fix duplicate problem, finish test logic
LOV-ING-U Oct 30, 2025
513c584
finish DB API, finally
LOV-ING-U Oct 30, 2025
c02b1fb
Make TimetableCourse folder
subir-sh Oct 30, 2025
f7dbe5b
Add get timetable detail (without controller logic)
subir-sh Oct 30, 2025
37ff5ce
Add delete timetablecourse
subir-sh Oct 30, 2025
cb6fa21
Add TimetableCourse exceptions
subir-sh Oct 30, 2025
95d6ad2
Add timetablecourse related tests (without generation)
subir-sh Oct 30, 2025
de688e9
initial commit
lee-seulchan Oct 31, 2025
7893df5
add CourseDto, CourseTimeDto
lee-seulchan Oct 31, 2025
7ecaf65
add SearchCourseRequest.kt, SearchCourseResponse.kt
lee-seulchan Oct 31, 2025
000f3a4
add CourseRepository.kt
lee-seulchan Oct 31, 2025
1921105
add CourseTimeRepository.kt
lee-seulchan Oct 31, 2025
472006d
add CourseService.kt, CourseException.kt, fix CourseDto.kt
lee-seulchan Oct 31, 2025
9a086b4
add CourseController.kt, fix CourseRepository.kt
lee-seulchan Oct 31, 2025
cb5c4f7
fix typo error
lee-seulchan Oct 31, 2025
a4d8f5e
fix logic
lee-seulchan Oct 31, 2025
03b9619
fix CourseTimeRepository.kt, CourseDto.kt
lee-seulchan Oct 31, 2025
3947f2e
add timetable (create, list, modify, delete)
Nekke0409 Oct 31, 2025
c8fad33
add course search test
lee-seulchan Oct 31, 2025
0e8b020
add pagination test
lee-seulchan Oct 31, 2025
8c8132c
TimetableCourse
subir-sh Oct 31, 2025
bb8e957
Course
subir-sh Oct 31, 2025
5adff8b
kihwan pr
LOV-ING-U Oct 31, 2025
deada08
Merge branch 'main' into ksh
subir-sh Oct 31, 2025
b31d2ab
Timetable
subir-sh Oct 31, 2025
5326b9d
kihwan pr
LOV-ING-U Oct 31, 2025
0505cf1
kkh
LOV-ING-U Oct 31, 2025
4cc3b63
d
LOV-ING-U Oct 31, 2025
94d65da
d
LOV-ING-U Oct 31, 2025
7ffd119
fix: working on small errors and integration
subir-sh Oct 31, 2025
2dd7a74
Merge branch 'main' into KiHwan
subir-sh Oct 31, 2025
f84e20a
TimeExtract API
subir-sh Oct 31, 2025
18e70c3
Integration
subir-sh Oct 31, 2025
637ca99
Test not working
subir-sh Oct 31, 2025
9b6e694
pass pagenation test
LOV-ING-U Oct 31, 2025
a6ee7a7
fix almost error
LOV-ING-U Oct 31, 2025
e5a0320
fix almost all errors
LOV-ING-U Oct 31, 2025
2353b89
fix all error except one
LOV-ING-U Oct 31, 2025
501d140
add test "should search for courses"
lee-seulchan Nov 2, 2025
5d6e594
fix test "should paginate correctly when searching for courses"
lee-seulchan Nov 2, 2025
5141a4c
Merge pull request #8 from LOV-ING-U/KiHwan
seulchan-lee Nov 2, 2025
9973842
Integrate TimetableCourseController into TimetableController
subir-sh Nov 2, 2025
17cf0aa
Merge pull request #9 from LOV-ING-U/lsh-test
subir-sh Nov 2, 2025
4f3f7d0
finish ktlintformat
LOV-ING-U Nov 2, 2025
ea868ce
Merge pull request #10 from LOV-ING-U/KiHwan
LOV-ING-U Nov 2, 2025
63c3298
back to origin
LOV-ING-U Nov 3, 2025
1c5ba25
ktlintformat, finally
LOV-ING-U Nov 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,17 @@ dependencies {
implementation("com.mysql:mysql-connector-j")
implementation("org.flywaydb:flyway-mysql")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.apache.poi:poi:5.2.5")
implementation("org.apache.poi:poi-ooxml:5.2.5")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("io.projectreactor.netty:reactor-netty")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:mysql")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.wafflestudio.spring2025.config

import io.netty.channel.ChannelOption
import org.springframework.beans.factory.annotation.Value
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.WebClient
import reactor.netty.http.client.HttpClient
import java.time.Duration

@Configuration
class WebClientConfig(
@Value("\${sugang.base-url}") private val baseUrl: String,
@Value("\${sugang.connect-timeout-ms:5000}") private val connectTimeoutMS: Int,
@Value("\${sugang.read-timeout-ms:15000}") private val readTimeoutMS: Int,
) {
@Bean
fun sugangWebClient(): WebClient {
val httpClient =
HttpClient
.create()
.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
connectTimeoutMS,
).responseTimeout(Duration.ofMillis(readTimeoutMS.toLong()))
return WebClient
.builder()
.baseUrl(
baseUrl,
).defaultHeader("User-Agent", "Mozilla/5.0")
.clientConnector(ReactorClientHttpConnector(httpClient))
.codecs { cfg ->
cfg.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)
}.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.wafflestudio.spring2025.course

import com.wafflestudio.spring2025.DomainException
import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatusCode

open class CourseException(
errorCode: Int,
httpStatusCode: HttpStatusCode,
msg: String,
) : DomainException(errorCode, httpStatusCode, msg) {
class InvalidSemesterException :
CourseException(
errorCode = 0,
httpStatusCode = HttpStatus.BAD_REQUEST,
msg = "Invalid semester",
)

class CourseNotFoundException :
CourseException(
errorCode = 0,
httpStatusCode = HttpStatus.NOT_FOUND,
msg = "Course not found",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.wafflestudio.spring2025.course.controller

import com.wafflestudio.spring2025.course.dto.course.SearchCourseRequest
import com.wafflestudio.spring2025.course.dto.course.SearchCourseResponse
import com.wafflestudio.spring2025.course.service.CourseService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v1/courses")
class CourseController(
private val courseService: CourseService,
) {
@GetMapping
fun search(
@RequestParam year: Int,
@RequestParam semester: String,
@RequestParam(name = "query", required = false) keyword: String?,
@RequestParam(name = "size", defaultValue = "20") limit: Int,
@RequestParam(name = "cursor", required = false) nextId: Long?,
): ResponseEntity<SearchCourseResponse> {
val request =
SearchCourseRequest(
year = year,
semester = semester,
keyword = keyword,
limit = limit,
nextId = nextId,
)
return ResponseEntity.ok(courseService.search(request))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.wafflestudio.spring2025.course.dto.course

import com.wafflestudio.spring2025.course.dto.coursetime.CourseTimeDto
import com.wafflestudio.spring2025.course.model.Course

data class CourseDto(
val id: Long,
val year: Int,
val semester: String,
val courseNumber: String,
val classNumber: String,
val title: String,
val subtitle: String?,
val credit: Int?,
val professor: String?,
val room: String?,
val category: String?,
val college: String?,
val department: String?,
val grade: Int?,
val procedure: String?,
val times: List<CourseTimeDto> = emptyList(),
) {
constructor(c: Course, times: List<CourseTimeDto>) : this(
id = c.id!!,
year = c.year,
semester = c.semester,
courseNumber = c.courseNumber,
classNumber = c.classNumber,
title = c.title,
subtitle = c.subtitle,
credit = c.credit,
professor = c.professor,
room = c.room,
category = c.category,
college = c.college,
department = c.department,
grade = c.grade,
procedure = c.procedure,
times = times,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wafflestudio.spring2025.course.dto.course

data class SearchCourseRequest(
val year: Int,
val semester: String,
val keyword: String? = null,
val limit: Int = 20,
val nextId: Long? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wafflestudio.spring2025.course.dto.course

data class SearchCourseResponse(
val data: List<CourseDto>,
val paging: Paging,
)

data class Paging(
val hasNext: Boolean,
val nextCursor: Long?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wafflestudio.spring2025.course.dto.coursetime

data class CourseTimeDto(
val weekday: Int,
val startMin: Int,
val endMin: Int,
val location: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.wafflestudio.spring2025.course.extract.controller

import com.wafflestudio.spring2025.course.extract.dto.CourseExtractDto
import com.wafflestudio.spring2025.course.extract.dto.CourseSyncRequestDto
import com.wafflestudio.spring2025.course.extract.dto.CourseSyncResultDto
import com.wafflestudio.spring2025.course.extract.service.CourseExtractService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile

@RestController
@RequestMapping("/course/extract")
class CourseExtractController(
private val courseExtractService: CourseExtractService,
) {
@PostMapping("/put")
fun putXlsInDB(
@RequestParam year: Int,
@RequestParam semester: String,
@RequestPart file: MultipartFile,
): ResponseEntity<CourseExtractDto> {
val inserted = courseExtractService.readInputXlsAndPutInDB(year, semester, file.inputStream)
return ResponseEntity.ok(CourseExtractDto(inserted))
}

@PostMapping("/sync")
fun sync(
@RequestBody req: CourseSyncRequestDto,
): ResponseEntity<CourseSyncResultDto> {
val result = courseExtractService.syncFromSugang(req.year, req.semCode)
return ResponseEntity.ok(result)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.wafflestudio.spring2025.course.extract.controller

import com.wafflestudio.spring2025.course.extract.dto.CourseTimeExtractDto
import com.wafflestudio.spring2025.course.extract.service.CourseTimeExtractService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile

@RestController
@RequestMapping("/course/extract")
class CourseTimeExtractController(
private val courseTimeExtractService: CourseTimeExtractService,
) {
@PostMapping("/times")
fun putXlsTimeInDB(
@RequestParam year: Int,
@RequestParam semester: String,
@RequestPart file: MultipartFile,
): ResponseEntity<CourseTimeExtractDto> {
val inserted =
courseTimeExtractService.importTimesFromXls(
year = year,
semester = semester,
inputStream = file.inputStream,
)
return ResponseEntity.ok(CourseTimeExtractDto(inserted))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wafflestudio.spring2025.course.extract.dto

data class CourseExtractDto(
val inserted: Int, // number of inserted on DB
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wafflestudio.spring2025.course.extract.dto

data class CourseSyncRequestDto(
val year: Int,
val semCode: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wafflestudio.spring2025.course.extract.dto

data class CourseSyncResultDto(
val year: Int,
val semester: String,
val fetchedBytes: Int,
val insertedCourses: Int,
val insertedCourseTimes: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wafflestudio.spring2025.course.extract.dto

class CourseTimeExtractDto(
val inserted: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.wafflestudio.spring2025.course.extract.repository

import com.wafflestudio.spring2025.course.model.Course
import org.springframework.data.repository.CrudRepository

interface CourseExtractRepository : CrudRepository<Course, Long> {
fun findByYearAndSemesterAndCourseNumberAndClassNumber(
year: Int,
semester: String,
courseNumber: String,
classNumber: String,
): Course?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wafflestudio.spring2025.course.extract.repository

import com.wafflestudio.spring2025.course.model.CourseTime
import org.springframework.data.repository.CrudRepository

interface CourseTimeExtractRepository : CrudRepository<CourseTime, Long> {
fun deleteByCourseId(courseId: Long): Long
}
Loading