-
Notifications
You must be signed in to change notification settings - Fork 0
#178 feat: 개설학과 firebase 적용 #179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,4 +9,5 @@ android { | |
|
|
||
| dependencies { | ||
| implementation(projects.domain.timetable) | ||
| testImplementation(libs.junit4) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.suwiki.data.timetable | ||
|
|
||
| data class OpenLectureRaw( | ||
| val number: Long = 0, // 번호 | ||
| val major: String = "", // 개설학과 | ||
| val grade: Int = 1, // 개설학년 | ||
| val className: String = "", // 과목명 | ||
| val classification: String = "", // 이수 구분 | ||
| val professor: String = "", | ||
| val time: String = "", | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package com.suwiki.data.timetable | ||
|
|
||
| import com.suwiki.common.model.timetable.Cell | ||
| import com.suwiki.common.model.timetable.TimetableDay | ||
|
|
||
| object TimetableUtil { | ||
| fun parseTimeTableString(input: String): List<Cell> { | ||
| val cellRegex = """([^(]+)\(([^)]+)\)""".toRegex() | ||
| val dayPeriodRegex = """([월화수목금토])([\d,]+)""".toRegex() | ||
|
|
||
| val result = input.split("),").flatMap { cellInput -> | ||
| val sanitizedInput = if (!cellInput.endsWith(")")) "$cellInput)" else cellInput | ||
| cellRegex.find(sanitizedInput)?.let { cellMatch -> | ||
| val (location, schedule) = cellMatch.destructured | ||
| val trimmedLocation = location.trim() | ||
| dayPeriodRegex.findAll(schedule).flatMap { dayPeriodMatch -> | ||
| val (day, periods) = dayPeriodMatch.destructured | ||
| val periodList = periods.split(",").map { it.toInt() } | ||
|
|
||
| periodList.groupConsecutive().map { (start, end) -> | ||
| Cell( | ||
| location = trimmedLocation, | ||
| day = when (day) { | ||
| "월" -> TimetableDay.MON | ||
| "화" -> TimetableDay.TUE | ||
| "수" -> TimetableDay.WED | ||
| "목" -> TimetableDay.THU | ||
| "금" -> TimetableDay.FRI | ||
| "토" -> TimetableDay.SAT | ||
| else -> throw IllegalArgumentException("Invalid day: $day") | ||
| }, | ||
| startPeriod = start, | ||
| endPeriod = end | ||
| ) | ||
| } | ||
| }.toList() | ||
| } ?: emptyList() | ||
| } | ||
|
|
||
| return result | ||
| } | ||
|
|
||
| private fun List<Int>.groupConsecutive(): List<Pair<Int, Int>> { | ||
| if (isEmpty()) return emptyList() | ||
| val result = mutableListOf<Pair<Int, Int>>() | ||
| var start = this[0] | ||
| var prev = start | ||
| for (i in 1 until size) { | ||
| if (this[i] != prev + 1) { | ||
| result.add(start to prev) | ||
| start = this[i] | ||
| } | ||
| prev = this[i] | ||
| } | ||
| result.add(start to prev) | ||
| return result | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.suwiki.data.timetable.datasource | ||
|
|
||
| import com.suwiki.common.model.timetable.OpenLecture | ||
| import kotlinx.coroutines.flow.Flow | ||
|
|
||
| interface LocalOpenLectureDataSource { | ||
| fun getOpenLectureListVersion(): Flow<Long?> | ||
|
|
||
| suspend fun setOpenLectureListVersion(version: Long) | ||
|
|
||
| fun getOpenLectureList( | ||
| lectureOrProfessorName: String? = null, | ||
| major: String? = null, | ||
| grade: Int? = null | ||
| ): Flow<List<OpenLecture>> | ||
|
|
||
| suspend fun updateAllLectures(lectures: List<OpenLecture>) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,8 @@ | ||
| package com.suwiki.data.timetable.datasource | ||
|
|
||
| import com.suwiki.common.model.timetable.OpenLectureData | ||
| import com.suwiki.data.timetable.OpenLectureRaw | ||
|
|
||
| interface RemoteOpenLectureDataSource { | ||
| suspend fun getOpenLectureList( | ||
| cursorId: Long, | ||
| size: Long, | ||
| keyword: String?, | ||
| major: String?, | ||
| grade: Int?, | ||
| ): OpenLectureData | ||
| suspend fun getOpenLectureListVersion(): Long | ||
| suspend fun getOpenLectureList(): List<OpenLectureRaw> | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,24 +1,85 @@ | ||||||||||||||||||||||||||||||||
| package com.suwiki.data.timetable.repository | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import com.suwiki.common.model.timetable.OpenLectureData | ||||||||||||||||||||||||||||||||
| import com.suwiki.common.model.timetable.OpenLecture | ||||||||||||||||||||||||||||||||
| import com.suwiki.data.timetable.TimetableUtil | ||||||||||||||||||||||||||||||||
| import com.suwiki.data.timetable.datasource.LocalOpenLectureDataSource | ||||||||||||||||||||||||||||||||
| import com.suwiki.data.timetable.datasource.RemoteOpenLectureDataSource | ||||||||||||||||||||||||||||||||
| import com.suwiki.domain.timetable.repository.OpenLectureRepository | ||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.async | ||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.coroutineScope | ||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.Flow | ||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.firstOrNull | ||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.map | ||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime | ||||||||||||||||||||||||||||||||
| import java.time.format.DateTimeFormatter | ||||||||||||||||||||||||||||||||
| import java.util.Locale | ||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| class OpenLectureRepositoryImpl @Inject constructor( | ||||||||||||||||||||||||||||||||
| private val remoteOpenLectureDataSource: RemoteOpenLectureDataSource, | ||||||||||||||||||||||||||||||||
| private val localOpenLectureDataSource: LocalOpenLectureDataSource, | ||||||||||||||||||||||||||||||||
| ) : OpenLectureRepository { | ||||||||||||||||||||||||||||||||
| override suspend fun getOpenLectureList( | ||||||||||||||||||||||||||||||||
| cursorId: Long, | ||||||||||||||||||||||||||||||||
| size: Long, | ||||||||||||||||||||||||||||||||
| keyword: String?, | ||||||||||||||||||||||||||||||||
| override fun getOpenLectureList( | ||||||||||||||||||||||||||||||||
| lectureOrProfessorName: String?, | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| major: String?, | ||||||||||||||||||||||||||||||||
| grade: Int?, | ||||||||||||||||||||||||||||||||
| ): OpenLectureData = remoteOpenLectureDataSource.getOpenLectureList( | ||||||||||||||||||||||||||||||||
| cursorId = cursorId, | ||||||||||||||||||||||||||||||||
| size = size, | ||||||||||||||||||||||||||||||||
| keyword = keyword, | ||||||||||||||||||||||||||||||||
| major = major, | ||||||||||||||||||||||||||||||||
| grade = grade, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| ): Flow<List<OpenLecture>> { | ||||||||||||||||||||||||||||||||
| return localOpenLectureDataSource.getOpenLectureList( | ||||||||||||||||||||||||||||||||
| lectureOrProfessorName = lectureOrProfessorName, | ||||||||||||||||||||||||||||||||
| major = major, | ||||||||||||||||||||||||||||||||
| grade = grade, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| override suspend fun checkNeedUpdate(): Boolean { | ||||||||||||||||||||||||||||||||
| val localVersion = localOpenLectureDataSource.getOpenLectureListVersion().firstOrNull() ?: return true | ||||||||||||||||||||||||||||||||
| val remoteVersion = remoteOpenLectureDataSource.getOpenLectureListVersion() | ||||||||||||||||||||||||||||||||
| return remoteVersion > localVersion | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네트워크 예외 처리 누락 - val remoteVersion = remoteOpenLectureDataSource.getOpenLectureListVersion()
- return remoteVersion > localVersion
+ val remoteVersion = runCatching { remoteOpenLectureDataSource.getOpenLectureListVersion() }
+ .getOrElse { return true } // 네트워크 실패 시 업데이트 필요로 간주
+ return remoteVersion > localVersion📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| override suspend fun updateAllLectures() = coroutineScope { | ||||||||||||||||||||||||||||||||
| val remoteOpenLectures = async { | ||||||||||||||||||||||||||||||||
| remoteOpenLectureDataSource.getOpenLectureList().map { | ||||||||||||||||||||||||||||||||
| OpenLecture( | ||||||||||||||||||||||||||||||||
| id = it.number, | ||||||||||||||||||||||||||||||||
| name = it.className, | ||||||||||||||||||||||||||||||||
| type = it.classification, | ||||||||||||||||||||||||||||||||
| major = it.major, | ||||||||||||||||||||||||||||||||
| grade = it.grade, | ||||||||||||||||||||||||||||||||
| professorName = it.professor, | ||||||||||||||||||||||||||||||||
| originalCellList = TimetableUtil.parseTimeTableString(it.time), | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| val remoteOpenLectureVersion = async { | ||||||||||||||||||||||||||||||||
| remoteOpenLectureDataSource.getOpenLectureListVersion() | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| localOpenLectureDataSource.updateAllLectures(remoteOpenLectures.await()) | ||||||||||||||||||||||||||||||||
| localOpenLectureDataSource.setOpenLectureListVersion(remoteOpenLectureVersion.await()) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 원격 버전·목록 2중 호출로 인한 오버헤드 |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| override suspend fun getLastUpdatedDate(): String? { | ||||||||||||||||||||||||||||||||
| return try { | ||||||||||||||||||||||||||||||||
| val version = localOpenLectureDataSource.getOpenLectureListVersion().firstOrNull().toString() | ||||||||||||||||||||||||||||||||
| if (version.length != 12) { | ||||||||||||||||||||||||||||||||
| return null | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm") | ||||||||||||||||||||||||||||||||
| val dateTime = LocalDateTime.parse(version, formatter) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| val koreanFormatter = DateTimeFormatter.ofPattern("yyyy년 M월 d일 H시 m분", Locale.KOREAN) | ||||||||||||||||||||||||||||||||
| dateTime.format(koreanFormatter) | ||||||||||||||||||||||||||||||||
| } catch (e: Exception) { | ||||||||||||||||||||||||||||||||
| null | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| override suspend fun getOpenMajor(): List<String> { | ||||||||||||||||||||||||||||||||
| return localOpenLectureDataSource.getOpenLectureList().map { list -> list.map { it.major } }.firstOrNull() ?: emptyList() | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,20 @@ | ||
| package com.suwiki.domain.timetable.repository | ||
|
|
||
| import com.suwiki.common.model.timetable.OpenLectureData | ||
| import com.suwiki.common.model.timetable.OpenLecture | ||
| import kotlinx.coroutines.flow.Flow | ||
|
|
||
| interface OpenLectureRepository { | ||
| suspend fun getOpenLectureList( | ||
| cursorId: Long, | ||
| size: Long, | ||
| keyword: String?, | ||
| major: String?, | ||
| grade: Int?, | ||
| ): OpenLectureData | ||
| fun getOpenLectureList( | ||
| lectureOrProfessorName: String? = null, | ||
| major: String? = null, | ||
| grade: Int? = null, | ||
| ): Flow<List<OpenLecture>> | ||
|
|
||
| suspend fun checkNeedUpdate(): Boolean | ||
|
|
||
| suspend fun updateAllLectures() | ||
|
|
||
| suspend fun getLastUpdatedDate(): String? | ||
|
|
||
| suspend fun getOpenMajor(): List<String> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.suwiki.domain.timetable.usecase | ||
|
|
||
| import com.suwiki.domain.common.runCatchingIgnoreCancelled | ||
| import com.suwiki.domain.timetable.repository.OpenLectureRepository | ||
| import javax.inject.Inject | ||
|
|
||
| class UpdateOpenLectureIfNeedUseCase @Inject constructor( | ||
| private val openLectureRepository: OpenLectureRepository, | ||
| ) { | ||
| suspend operator fun invoke(): Result<Unit> = runCatchingIgnoreCancelled { | ||
| if(openLectureRepository.checkNeedUpdate().not()) return@runCatchingIgnoreCancelled | ||
| openLectureRepository.updateAllLectures() | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.suwiki.local.common.database.converter | ||
|
|
||
| import androidx.room.TypeConverter | ||
| import com.suwiki.local.common.database.entity.CellEntity | ||
| import kotlinx.serialization.encodeToString | ||
| import kotlinx.serialization.json.Json | ||
|
|
||
| class OpenLectureConverter { | ||
| private val json = Json { ignoreUnknownKeys = true } | ||
|
|
||
| @TypeConverter | ||
| fun fromCellList(value: List<CellEntity>): String { | ||
| return json.encodeToString(value) | ||
| } | ||
|
|
||
| @TypeConverter | ||
| fun toCellList(value: String): List<CellEntity> { | ||
| return json.decodeFromString(value) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.suwiki.local.common.database.dao | ||
|
|
||
| import androidx.room.Dao | ||
| import androidx.room.Insert | ||
| import androidx.room.OnConflictStrategy | ||
| import androidx.room.Query | ||
| import androidx.room.Transaction | ||
| import com.suwiki.local.common.database.entity.OpenLectureEntity | ||
| import kotlinx.coroutines.flow.Flow | ||
|
|
||
| @Dao | ||
| interface OpenLectureDao { | ||
|
|
||
| @Query( | ||
| """ | ||
| SELECT * FROM OpenLectureEntity | ||
| WHERE (:lectureOrProfessorName IS NULL OR name LIKE '%' || :lectureOrProfessorName || '%' OR professorName LIKE '%' || :lectureOrProfessorName || '%') | ||
| AND (:major IS NULL OR major LIKE '%' || :major || '%') | ||
| AND (:grade IS NULL OR grade = :grade) | ||
| """ | ||
| ) | ||
| fun searchLectures( | ||
| lectureOrProfessorName: String? = null, | ||
| major: String? = null, | ||
| grade: Int? = null | ||
| ): Flow<List<OpenLectureEntity>> | ||
|
|
||
| @Insert(onConflict = OnConflictStrategy.REPLACE) | ||
| suspend fun insertLectures(lectures: List<OpenLectureEntity>) | ||
|
|
||
| @Query("DELETE FROM OpenLectureEntity") | ||
| suspend fun deleteAllLectures() | ||
|
|
||
| @Transaction | ||
| suspend fun updateAllLectures(lectures: List<OpenLectureEntity>) { | ||
| deleteAllLectures() | ||
| insertLectures(lectures) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.suwiki.local.common.database.database | ||
|
|
||
| import androidx.room.Database | ||
| import androidx.room.RoomDatabase | ||
| import androidx.room.TypeConverters | ||
| import com.suwiki.local.common.database.converter.OpenLectureConverter | ||
| import com.suwiki.local.common.database.dao.OpenLectureDao | ||
| import com.suwiki.local.common.database.entity.OpenLectureEntity | ||
|
|
||
|
|
||
| @Database(entities = [OpenLectureEntity::class], version = 1) | ||
| @TypeConverters(OpenLectureConverter::class) | ||
| abstract class OpenLectureDatabase : RoomDatabase() { | ||
| abstract fun openLectureDao(): OpenLectureDao | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q. 원래 이런 로직이 있었나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0f80dbf#diff-499cc6459302a199d0e9dd8e67ce0caa373dce6e8f16563d39fdc154517f0dd4
참고 커밋에 해당 로직이 있어서 추가 했습니다