Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.into.websoso.ui.splash.UiEffect.NavigateToMain
import com.into.websoso.ui.splash.UiEffect.ShowDialog
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -51,6 +52,8 @@ class SplashViewModel
}

private suspend fun handleAutoLogin() {
delay(1000)

if (shouldRefresh()) {
_uiEffect.send(NavigateToLogin)
return
Expand Down
19 changes: 8 additions & 11 deletions core/database/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ android {
}

dependencies {
// 데이터베이스 관련 라이브러리
// 데이터 레이어 의존성
implementation(projects.data.library)

// 페이징 관련 의존성
implementation(libs.paging.runtime)

// 데이터베이스 관련 의존성
implementation(libs.room.paging)
implementation(libs.room.ktx)
implementation(libs.room.runtime)
kapt(libs.room.compiler)

// 페이징3
val paging_version = "3.3.6"
implementation("androidx.room:room-paging:2.5.1") // Room 최신 버전에 맞게 버전 확인


implementation("androidx.paging:paging-runtime:$paging_version")

// alternatively - without Android dependencies for tests
implementation("androidx.paging:paging-common:$paging_version")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.into.websoso.core.database

import androidx.room.TypeConverter

class Converters {
internal class Converters {
@TypeConverter
fun fromString(value: String): List<String> = if (value.isEmpty()) emptyList() else value.split(",")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,17 @@
package com.into.websoso.core.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.into.websoso.core.database.dao.FilteredNovelDao
import com.into.websoso.core.database.dao.NovelDao
import com.into.websoso.core.database.entity.InDatabaseFilteredNovelEntity
import com.into.websoso.core.database.entity.InDatabaseNovelEntity
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import com.into.websoso.core.database.datasource.library.dao.NovelDao
import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity

@Database(
entities = [InDatabaseNovelEntity::class, InDatabaseFilteredNovelEntity::class],
version = 4,
entities = [InDatabaseNovelEntity::class],
version = 5,
exportSchema = false,
)
@TypeConverters(Converters::class)
internal abstract class WebsosoDatabase : RoomDatabase() {
internal abstract fun novelDao(): NovelDao

internal abstract fun filteredNovelDao(): FilteredNovelDao
}

@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
@Provides
@Singleton
internal fun provideDatabase(
@ApplicationContext context: Context,
): WebsosoDatabase =
Room
.databaseBuilder(
context,
WebsosoDatabase::class.java,
"websoso.db",
).build()
}
Comment on lines 9 to 17
Copy link

@coderabbitai coderabbitai bot Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

데이터베이스 스키마 단순화가 잘 되었습니다.

FilteredNovelEntity 제거로 스키마가 단순해졌고, 버전을 5로 증가시킨 것이 적절합니다. 다만 마이그레이션 처리가 필요합니다.

다음 스크립트로 마이그레이션 구현을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# 데이터베이스 마이그레이션 확인
rg -A 10 "Migration.*4.*5" --type kotlin
ast-grep --pattern 'val MIGRATION_4_5 = $_'

Length of output: 88


마이그레이션 구현 필요: 버전 4→5 누락 확인

RoomDatabase 버전을 4에서 5로 올리셨지만, 해당 마이그레이션 코드(예: MIGRATION_4_5)와 이를 addMigrations()로 등록하는 부분이 발견되지 않습니다. 스키마 변경 시 데이터 손실을 방지하려면 반드시 마이그레이션을 추가해 주세요.

점검 항목:

  • MIGRATION_4_5 객체(또는 함수) 정의 여부 확인
  • Room.databaseBuilder(...).addMigrations(MIGRATION_4_5) 호출 여부 확인
🤖 Prompt for AI Agents
In core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt
around lines 9 to 17, the migration from version 4 to 5 is missing. Define a
MIGRATION_4_5 object that specifies the schema changes between these versions,
then register this migration by adding addMigrations(MIGRATION_4_5) to the
Room.databaseBuilder call to ensure data integrity during the upgrade.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

•fallbackToDestructiveMigration()은?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@s9hn 좋은 지적입니다! fallbackToDestructiveMigration()을 사용하는 것이 이 경우에는 더 적합할 수 있습니다.

fallbackToDestructiveMigration()은 다음과 같은 상황에서 적절한 선택입니다:

  1. 캐시된 데이터인 경우: InDatabaseFilteredNovelEntity는 필터링된 소설 데이터를 캐시하는 용도로 보이며, 이는 다시 가져올 수 있는 데이터입니다.

  2. PR 목표와 부합: 이번 PR에서 "전체 사용자 서재 데이터를 백그라운드에서 가져오는 무거운 작업을 피하고, 사용자가 명시적으로 요청한 데이터만 캐시"하는 것이 목표라고 하셨는데, 기존 캐시 데이터를 버리고 새로 시작하는 것이 오히려 의도에 맞습니다.

  3. 데이터 손실이 허용되는 경우: 사용자의 중요한 데이터가 아닌 성능 최적화를 위한 캐시 데이터이므로 손실되어도 문제없습니다.

따라서 DatabaseModule에서 다음과 같이 설정하는 것을 추천합니다:

Room.databaseBuilder(context, WebsosoDatabase::class.java, "websoso.db")
    .fallbackToDestructiveMigration()
    .build()

이렇게 하면 명시적인 마이그레이션 코드 없이도 안전하게 스키마 변경을 처리할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# DatabaseModule에서 fallbackToDestructiveMigration 사용 여부 확인
rg "fallbackToDestructiveMigration" --type kotlin

Length of output: 49


현재 코드베이스에서 fallbackToDestructiveMigration()이 사용되지 않고 있습니다. DatabaseModule의 현재 구현을 확인해서 어떻게 설정되어 있는지 보겠습니다.


🏁 Script executed:

#!/bin/bash
# DatabaseModule 구현 확인
rg -A 10 "Room\.databaseBuilder" --type kotlin

Length of output: 48


🏁 Script executed:

#!/bin/bash
# DatabaseModule 파일 찾기
fd DatabaseModule.kt

# Room 관련 코드 찾기
rg "Room\." --type kotlin -A 3

# 데이터베이스 관련 모듈 찾기
rg "provideDatabase" --type kotlin -A 5

Length of output: 828


🏁 Script executed:

#!/bin/bash
# DatabaseModule 전체 내용 확인
cat core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt

Length of output: 829


@s9hn 맞습니다! 현재 DatabaseModule을 보니 마이그레이션 처리가 전혀 없는 상태네요. fallbackToDestructiveMigration()을 사용하는 것이 이 상황에서 가장 적절한 해결책입니다.

현재 DatabaseModule을 다음과 같이 수정하시면 됩니다:

@Provides
@Singleton
internal fun provideDatabase(
    @ApplicationContext context: Context,
): WebsosoDatabase =
    Room
        .databaseBuilder(
            context,
            WebsosoDatabase::class.java,
            "websoso.db",
        )
        .fallbackToDestructiveMigration()
        .build()

이 방법이 적합한 이유:

  1. 제거된 엔티티의 성격: InDatabaseFilteredNovelEntity는 필터링된 소설 데이터를 캐시하는 용도로, 사용자의 핵심 데이터가 아닙니다.

  2. PR 목표와 일치: "백그라운드에서 모든 사용자 서재 데이터를 가져오는 무거운 작업을 피하고, 사용자가 명시적으로 요청한 데이터만 캐시"한다는 목표에 부합합니다.

  3. 개발 효율성: 복잡한 마이그레이션 로직을 작성할 필요 없이 깔끔하게 스키마 변경을 처리할 수 있습니다.

  4. 데이터 특성: 캐시 데이터는 언제든 다시 가져올 수 있으므로 손실되어도 문제없습니다.

명시적 마이그레이션 대신 fallbackToDestructiveMigration()을 사용하는 것이 이번 리팩토링의 의도에 더 적합한 선택인 것 같습니다.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.into.websoso.core.database.datasource.library

import androidx.paging.PagingSource
import com.into.websoso.core.database.datasource.library.dao.NovelDao
import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity
import com.into.websoso.core.database.datasource.library.mapper.toData
import com.into.websoso.core.database.datasource.library.mapper.toNovelDatabase
import com.into.websoso.core.database.datasource.library.paging.mapValue
import com.into.websoso.data.library.datasource.LibraryLocalDataSource
import com.into.websoso.data.library.model.NovelEntity
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Inject
import javax.inject.Singleton

internal class DefaultLibraryLocalDataSource
@Inject
constructor(
private val novelDao: NovelDao,
) : LibraryLocalDataSource {
override suspend fun insertNovels(novels: List<NovelEntity>) {
val offset = novelDao.selectNovelsCount()
novelDao.insertNovels(
novels.mapIndexed { index, novelEntity ->
novelEntity.toNovelDatabase(offset + index)
},
)
}

override fun selectAllNovels(): PagingSource<Int, NovelEntity> = novelDao.selectAllNovels().mapValue(InDatabaseNovelEntity::toData)

override suspend fun deleteAllNovels() {
novelDao.deleteAllNovels()
}
}

@Module
@InstallIn(SingletonComponent::class)
internal interface LibraryDataSourceModule {
@Binds
@Singleton
fun bindLibraryDataSource(defaultLibraryLocalDataSource: DefaultLibraryLocalDataSource): LibraryLocalDataSource
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
package com.into.websoso.core.database.dao
package com.into.websoso.core.database.datasource.library.dao

import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.into.websoso.core.database.WebsosoDatabase
import com.into.websoso.core.database.entity.InDatabaseNovelEntity
import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Dao
interface NovelDao {
@Query("SELECT * FROM novels ORDER BY userNovelId DESC")
internal interface NovelDao {
@Query("SELECT * FROM novels ORDER BY sortIndex ASC")
fun selectAllNovels(): PagingSource<Int, InDatabaseNovelEntity>

@Query("SELECT COUNT(*) FROM novels")
suspend fun selectNovelsCount(): Int

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertNovels(novels: List<InDatabaseNovelEntity>)

@Query("DELETE FROM novels")
suspend fun clearAll()
suspend fun deleteAllNovels()
}

@Module
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.into.websoso.core.database.entity
package com.into.websoso.core.database.datasource.library.entity

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "novels")
data class InDatabaseNovelEntity(
internal data class InDatabaseNovelEntity(
@PrimaryKey val userNovelId: Long,
val novelId: Long,
val title: String,
val sortIndex: Int,
val novelImage: String,
val novelRating: Float,
val readStatus: String,
Expand All @@ -19,3 +20,4 @@ data class InDatabaseNovelEntity(
val keywords: List<String>,
val myFeeds: List<String>,
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.into.websoso.core.database.datasource.library.mapper

import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity
import com.into.websoso.data.library.model.NovelEntity

internal fun NovelEntity.toNovelDatabase(index: Int): InDatabaseNovelEntity =
InDatabaseNovelEntity(
userNovelId = userNovelId,
novelId = novelId,
title = title,
novelImage = novelImage,
novelRating = novelRating,
readStatus = readStatus,
isInterest = isInterest,
userNovelRating = userNovelRating,
attractivePoints = attractivePoints,
startDate = startDate,
endDate = endDate,
keywords = keywords,
myFeeds = myFeeds,
sortIndex = index,
)

internal fun InDatabaseNovelEntity.toData(): NovelEntity =
NovelEntity(
userNovelId = userNovelId,
novelId = novelId,
title = title,
novelImage = novelImage,
novelRating = novelRating,
readStatus = readStatus,
isInterest = isInterest,
userNovelRating = userNovelRating,
attractivePoints = attractivePoints,
startDate = startDate,
endDate = endDate,
keywords = keywords,
myFeeds = myFeeds,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.into.websoso.core.database.datasource.library.paging

import androidx.paging.PagingSource
import androidx.paging.PagingState

internal fun <K : Any, From : Any, To : Any> PagingSource<K, From>.mapValue(mapper: suspend (From) -> To): PagingSource<K, To> =
MapValuePagingSource(this, mapper)

internal class MapValuePagingSource<Key : Any, From : Any, To : Any>(
private val targetSource: PagingSource<Key, From>,
private val mapper: suspend (From) -> To,
) : PagingSource<Key, To>() {
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, To> =
when (val result = targetSource.load(params)) {
is LoadResult.Page -> LoadResult.Page(
data = result.data.map { mapper(it) },
prevKey = result.prevKey,
nextKey = result.nextKey,
itemsBefore = result.itemsBefore,
itemsAfter = result.itemsAfter,
)

is LoadResult.Error -> LoadResult.Error(result.throwable)
is LoadResult.Invalid -> LoadResult.Invalid()
}

override val jumpingSupported: Boolean get() = targetSource.jumpingSupported

override fun getRefreshKey(state: PagingState<Key, To>): Key? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.into.websoso.core.database.di

import android.content.Context
import androidx.room.Room
import com.into.websoso.core.database.WebsosoDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
@Provides
@Singleton
internal fun provideDatabase(
@ApplicationContext context: Context,
): WebsosoDatabase =
Room
.databaseBuilder(
context,
WebsosoDatabase::class.java,
"websoso.db",
).fallbackToDestructiveMigration()
.build()
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ internal class DefaultLibraryFilterDataSource
}
}.distinctUntilChanged()

override suspend fun updateLibraryFilter(params: LibraryFilter) {
override suspend fun updateLibraryFilter(libraryFilter: LibraryFilter) {
val encodedJsonString = withContext(dispatcher) {
Json.encodeToString(
params.toPreferences(),
libraryFilter.toPreferences(),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
internal data class LibraryFilterPreferences(
val sortCriteria: String,
val isInterested: Boolean,
val readStatuses: Map<String, Boolean>,
val attractivePoints: Map<String, Boolean>,
val readStatuses: List<String>,
val attractivePoints: List<String>,
val novelRating: Float,
)
Loading