From ad5c25784af820a45816135944570ed389a496e9 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 17:02:58 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20Question=20=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EC=97=90=20=EC=A2=8B=EC=95=84=EC=9A=94/=ED=91=BC=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Question 도메인 모델에 isSolved, isFavorite 필수 필드로 추가 - QuestionResponse 데이터 모델에 is_favorited, is_solved 필드 추가 - toDomain() 매핑 로직에 새 필드 반영 🤖 Generated with [Firebender](https://firebender.com) Co-Authored-By: Firebender --- .../com/peto/droidmorning/data/model/QuestionResponse.kt | 6 ++++++ .../kotlin/com/peto/droidmorning/domain/model/Question.kt | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt index 33a7a6f..3491656 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt @@ -17,6 +17,10 @@ data class QuestionResponse( val createdAt: Instant, @SerialName("updated_at") val updatedAt: Instant, + @SerialName("is_favorited") + val isFavorited: Boolean = false, + @SerialName("is_solved") + val isSolved: Boolean = false, ) { fun toDomain(): Question = Question( @@ -26,5 +30,7 @@ data class QuestionResponse( sourceUrl = sourceUrl, createdAt = createdAt, updatedAt = updatedAt, + isSolved = isSolved, + isFavorite = isFavorited, ) } diff --git a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt index 08832b9..8c1cbc4 100644 --- a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt +++ b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt @@ -9,8 +9,8 @@ data class Question( val sourceUrl: String, val createdAt: Instant, val updatedAt: Instant, - val isSolved: Boolean = false, - val isFavorite: Boolean = false, + val isSolved: Boolean, + val isFavorite: Boolean, ) { fun isTitleMatched(query: SearchQuery): Boolean = title.contains(query.value, ignoreCase = true) } From 1a326a5e58a040e1534c4520736316aa2317d04e Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 17:03:11 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EB=B3=84=20=EC=A7=88=EB=AC=B8=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20RPC=20=ED=95=A8=EC=88=98=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Supabase RPC 함수(fetch_questions) 사용으로 변경 - Auth 모듈 의존성 추가하여 사용자 ID 기반 조회 - 좋아요/푼 문제 상태가 포함된 질문 목록 조회 가능 - DataSourceModule에 Auth 의존성 주입 추가 🤖 Generated with [Firebender](https://firebender.com) Co-Authored-By: Firebender --- .../remote/DefaultRemoteQuestionDataSource.kt | 18 ++++++++++++------ .../droidmorning/data/di/DataSourceModule.kt | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt index 48ffd08..22eb9dd 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt @@ -1,20 +1,24 @@ package com.peto.droidmorning.data.datasource.question.remote import com.peto.droidmorning.data.model.QuestionResponse +import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.postgrest.Postgrest import io.github.jan.supabase.postgrest.query.Columns import io.github.jan.supabase.postgrest.query.Order import io.github.jan.supabase.postgrest.query.filter.TextSearchType +import io.github.jan.supabase.postgrest.rpc class DefaultRemoteQuestionDataSource( private val postgrest: Postgrest, + private val auth: Auth ) : RemoteQuestionDataSource { - override suspend fun fetchQuestions(): List = - postgrest - .from(TABLE_NAME) - .select(columns = Columns.ALL) { - order(column = ORDER_BY_CREATED_AT, order = Order.DESCENDING) - }.decodeList() + override suspend fun fetchQuestions(): List { + val uid = auth.currentSessionOrNull()?.user?.id ?: return emptyList() + val params = mapOf(RPC_FETCH_QUESTIONS_PARAM_NAME to uid) + return postgrest + .rpc(RPC_FETCH_QUESTIONS, params) + .decodeList() + } override suspend fun fetchQuestionsByCategory(category: String): List = postgrest @@ -37,6 +41,8 @@ class DefaultRemoteQuestionDataSource( }.decodeList() companion object { + private const val RPC_FETCH_QUESTIONS = "fetch_questions" + private const val RPC_FETCH_QUESTIONS_PARAM_NAME = "uid" private const val TABLE_NAME = "questions" private const val CATEGORY_COLUMN = "category" private const val FTS_COLUMN = "fts" diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/di/DataSourceModule.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/di/DataSourceModule.kt index cd10026..ed975de 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/di/DataSourceModule.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/di/DataSourceModule.kt @@ -12,5 +12,5 @@ internal val dataSourceModule = module { single { DefaultLocalAuthDataSource(get()) } single { DefaultRemoteAuthDataSource(get()) } - single { DefaultRemoteQuestionDataSource(get()) } + single { DefaultRemoteQuestionDataSource(get(), get()) } } From d2fd8ee52b15062709c7ee0024c2cab702cce0ec Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 17:10:47 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20'favorite'=20=EC=9A=A9=EC=96=B4?= =?UTF-8?q?=EB=A5=BC=20'like'=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../droidmorning/question/QuestionScreen.kt | 16 ++++++------- .../question/component/QuestionFilterChips.kt | 16 ++++++------- .../question/component/QuestionList.kt | 24 +++++++++---------- .../question/vm/QuestionUiState.kt | 6 ++--- .../question/vm/QuestionViewModel.kt | 12 +++++----- .../data/model/QuestionResponse.kt | 4 ++-- .../designsystem/component/QuestionCard.kt | 16 ++++++------- .../peto/droidmorning/domain/model/Filter.kt | 4 ++-- .../droidmorning/domain/model/Question.kt | 2 +- .../droidmorning/domain/model/Questions.kt | 8 +++---- 10 files changed, 54 insertions(+), 54 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/QuestionScreen.kt b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/QuestionScreen.kt index 344c29d..5d5e461 100644 --- a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/QuestionScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/QuestionScreen.kt @@ -50,7 +50,7 @@ fun QuestionScreen( uiState.searchQuery, uiState.selectedCategories, uiState.showSolvedOnly, - uiState.showFavoritesOnly, + uiState.showLikedOnly, ) { if (listState.firstVisibleItemIndex > 0) { listState.scrollToItem(0) @@ -79,9 +79,9 @@ fun QuestionScreen( onCategoryToggle = viewModel::onCategoryToggle, onToggleCategoryFilters = viewModel::onToggleCategoryFilters, onSolvedFilterToggle = viewModel::onSolvedFilterToggle, - onFavoritesFilterToggle = viewModel::onFavoritesFilterToggle, + onLikedFilterToggle = viewModel::onLikedFilterToggle, onQuestionClick = {}, - onFavoriteToggle = viewModel::onFavoriteToggle, + onLikeToggle = viewModel::onLikeToggle, ) } @@ -93,9 +93,9 @@ private fun QuestionScreenContent( onCategoryToggle: (Category) -> Unit, onToggleCategoryFilters: () -> Unit, onSolvedFilterToggle: () -> Unit, - onFavoritesFilterToggle: () -> Unit, + onLikedFilterToggle: () -> Unit, onQuestionClick: (Long) -> Unit, - onFavoriteToggle: (Long) -> Unit, + onLikeToggle: (Long) -> Unit, ) { Column( modifier = @@ -115,10 +115,10 @@ private fun QuestionScreenContent( QuestionFilterChips( selectedCategories = uiState.selectedCategories, showSolvedOnly = uiState.showSolvedOnly, - showFavoritesOnly = uiState.showFavoritesOnly, + showLikedOnly = uiState.showLikedOnly, onToggleCategoryFilters = onToggleCategoryFilters, onSolvedFilterToggle = onSolvedFilterToggle, - onFavoritesFilterToggle = onFavoritesFilterToggle, + onLikedFilterToggle = onLikedFilterToggle, ) Spacer(modifier = Modifier.height(Dimen.spacingMd)) @@ -156,7 +156,7 @@ private fun QuestionScreenContent( QuestionList( questions = uiState.filteredQuestions, onQuestionClick = onQuestionClick, - onFavoriteToggle = onFavoriteToggle, + onLikeToggle = onLikeToggle, listState = listState, ) } diff --git a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionFilterChips.kt b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionFilterChips.kt index d7718cd..ae77cd9 100644 --- a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionFilterChips.kt +++ b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionFilterChips.kt @@ -27,10 +27,10 @@ import org.jetbrains.compose.resources.stringResource fun QuestionFilterChips( selectedCategories: ImmutableSet, showSolvedOnly: Boolean, - showFavoritesOnly: Boolean, + showLikedOnly: Boolean, onToggleCategoryFilters: () -> Unit, onSolvedFilterToggle: () -> Unit, - onFavoritesFilterToggle: () -> Unit, + onLikedFilterToggle: () -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -63,8 +63,8 @@ fun QuestionFilterChips( CategoryFilterChip( text = stringResource(Res.string.question_filter_favorites), - selected = showFavoritesOnly, - onClick = onFavoritesFilterToggle, + selected = showLikedOnly, + onClick = onLikedFilterToggle, leadingIcon = { Icon( imageVector = Icons.Filled.Star, @@ -82,10 +82,10 @@ private fun QuestionFilterChipsPreview() { QuestionFilterChips( selectedCategories = persistentSetOf(), showSolvedOnly = false, - showFavoritesOnly = false, + showLikedOnly = false, onToggleCategoryFilters = {}, onSolvedFilterToggle = {}, - onFavoritesFilterToggle = {}, + onLikedFilterToggle = {}, ) } } @@ -97,10 +97,10 @@ private fun QuestionFilterChipsSelectedPreview() { QuestionFilterChips( selectedCategories = persistentSetOf(Category.Kotlin, Category.Android), showSolvedOnly = true, - showFavoritesOnly = true, + showLikedOnly = true, onToggleCategoryFilters = {}, onSolvedFilterToggle = {}, - onFavoritesFilterToggle = {}, + onLikedFilterToggle = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionList.kt b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionList.kt index 2065a99..3badd3e 100644 --- a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionList.kt +++ b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/component/QuestionList.kt @@ -22,7 +22,7 @@ import kotlin.time.Instant fun QuestionList( questions: ImmutableList, onQuestionClick: (Long) -> Unit, - onFavoriteToggle: (Long) -> Unit, + onLikeToggle: (Long) -> Unit, modifier: Modifier = Modifier, listState: LazyListState = rememberLazyListState(), ) { @@ -40,9 +40,9 @@ fun QuestionList( title = question.title, category = question.category, isSolved = question.isSolved, - isFavorite = question.isFavorite, + isLiked = question.isLiked, onClick = { onQuestionClick(question.id) }, - onFavoriteClick = { onFavoriteToggle(question.id) }, + onLikeClick = { onLikeToggle(question.id) }, ) } } @@ -55,7 +55,7 @@ private fun QuestionListEmptyPreview() { QuestionList( questions = persistentListOf(), onQuestionClick = {}, - onFavoriteToggle = {}, + onLikeToggle = {}, ) } } @@ -75,11 +75,11 @@ private fun QuestionListSinglePreview() { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = false, - isFavorite = false, + isLiked = false, ), ), onQuestionClick = {}, - onFavoriteToggle = {}, + onLikeToggle = {}, ) } } @@ -99,7 +99,7 @@ private fun QuestionListMultiplePreview() { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = true, - isFavorite = true, + isLiked = true, ), Question( id = 2L, @@ -109,7 +109,7 @@ private fun QuestionListMultiplePreview() { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = false, - isFavorite = true, + isLiked = true, ), Question( id = 3L, @@ -119,7 +119,7 @@ private fun QuestionListMultiplePreview() { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = true, - isFavorite = false, + isLiked = false, ), Question( id = 4L, @@ -129,7 +129,7 @@ private fun QuestionListMultiplePreview() { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = false, - isFavorite = false, + isLiked = false, ), Question( id = 5L, @@ -139,11 +139,11 @@ private fun QuestionListMultiplePreview() { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = false, - isFavorite = false, + isLiked = false, ), ), onQuestionClick = {}, - onFavoriteToggle = {}, + onLikeToggle = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt index 1fcf30b..63ae382 100644 --- a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt +++ b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt @@ -23,7 +23,7 @@ data class QuestionUiState( val selectedCategories: ImmutableSet get() = filter.categories.toSet().toImmutableSet() val showSolvedOnly: Boolean get() = filter.solved - val showFavoritesOnly: Boolean get() = filter.liked + val showLikedOnly: Boolean get() = filter.liked val filteredQuestions: ImmutableList get() { @@ -48,9 +48,9 @@ data class QuestionUiState( fun clearSolvedFilter(): QuestionUiState = copy(filter = filter.clearSolvedFilter()) - fun applyFavoritesFilter(): QuestionUiState = copy(filter = filter.applyFavoritesFilter()) + fun applyLikedFilter(): QuestionUiState = copy(filter = filter.applyLikedFilter()) - fun clearFavoritesFilter(): QuestionUiState = copy(filter = filter.clearFavoritesFilter()) + fun clearLikedFilter(): QuestionUiState = copy(filter = filter.clearLikedFilter()) fun loading(isLoading: Boolean): QuestionUiState = copy(isLoading = isLoading) diff --git a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt index 62a8590..8e1bfd9 100644 --- a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt @@ -59,12 +59,12 @@ class QuestionViewModel( applyFilter() } - fun onFavoritesFilterToggle() { + fun onLikedFilterToggle() { _uiState.update { - if (it.showFavoritesOnly) { - it.clearFavoritesFilter() + if (it.showLikedOnly) { + it.clearLikedFilter() } else { - it.applyFavoritesFilter() + it.applyLikedFilter() } } applyFilter() @@ -82,8 +82,8 @@ class QuestionViewModel( } } - fun onFavoriteToggle(questionId: Long) { - // TODO: Implement favorite toggle + fun onLikeToggle(questionId: Long) { + // TODO: Implement like toggle } private fun loadQuestions() { diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt index 3491656..d8f7eaa 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/QuestionResponse.kt @@ -18,7 +18,7 @@ data class QuestionResponse( @SerialName("updated_at") val updatedAt: Instant, @SerialName("is_favorited") - val isFavorited: Boolean = false, + val isLiked: Boolean = false, @SerialName("is_solved") val isSolved: Boolean = false, ) { @@ -31,6 +31,6 @@ data class QuestionResponse( createdAt = createdAt, updatedAt = updatedAt, isSolved = isSolved, - isFavorite = isFavorited, + isLiked = isLiked, ) } diff --git a/designsystem/src/commonMain/kotlin/com/peto/droidmorning/designsystem/component/QuestionCard.kt b/designsystem/src/commonMain/kotlin/com/peto/droidmorning/designsystem/component/QuestionCard.kt index b5a6b76..f098941 100644 --- a/designsystem/src/commonMain/kotlin/com/peto/droidmorning/designsystem/component/QuestionCard.kt +++ b/designsystem/src/commonMain/kotlin/com/peto/droidmorning/designsystem/component/QuestionCard.kt @@ -46,9 +46,9 @@ fun QuestionCard( title: String, category: Category, isSolved: Boolean, - isFavorite: Boolean, + isLiked: Boolean, onClick: () -> Unit, - onFavoriteClick: () -> Unit, + onLikeClick: () -> Unit, modifier: Modifier = Modifier, ) { InteractiveCard( @@ -92,19 +92,19 @@ fun QuestionCard( } IconButton( - onClick = onFavoriteClick, + onClick = onLikeClick, content = { Icon( - imageVector = if (isFavorite) Icons.Filled.Star else Icons.Outlined.Star, + imageVector = if (isLiked) Icons.Filled.Star else Icons.Outlined.Star, contentDescription = stringResource( - if (isFavorite) { + if (isLiked) { DesignRes.string.question_card_favorite_remove } else { DesignRes.string.question_card_favorite_add }, ), - tint = if (isFavorite) Warning else MutedForeground, + tint = if (isLiked) Warning else MutedForeground, modifier = Modifier.size(Dimen.iconSm), ) }, @@ -134,9 +134,9 @@ private fun QuestionCardPreview( title = state.title, category = state.category, isSolved = state.isSolved, - isFavorite = state.isFavorite, + isLiked = state.isFavorite, onClick = {}, - onFavoriteClick = {}, + onLikeClick = {}, ) } } diff --git a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Filter.kt b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Filter.kt index e3c20fd..fe8e5cf 100644 --- a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Filter.kt +++ b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Filter.kt @@ -22,7 +22,7 @@ data class Filter( fun clearSolvedFilter(): Filter = copy(solved = false) - fun applyFavoritesFilter(): Filter = copy(liked = true) + fun applyLikedFilter(): Filter = copy(liked = true) - fun clearFavoritesFilter(): Filter = copy(liked = false) + fun clearLikedFilter(): Filter = copy(liked = false) } diff --git a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt index 8c1cbc4..14ef2e2 100644 --- a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt +++ b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Question.kt @@ -10,7 +10,7 @@ data class Question( val createdAt: Instant, val updatedAt: Instant, val isSolved: Boolean, - val isFavorite: Boolean, + val isLiked: Boolean, ) { fun isTitleMatched(query: SearchQuery): Boolean = title.contains(query.value, ignoreCase = true) } diff --git a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Questions.kt b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Questions.kt index 906f576..34a3bfa 100644 --- a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Questions.kt +++ b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/model/Questions.kt @@ -13,7 +13,7 @@ data class Questions( filterBySearchQuery(filter.searchQuery) .filterByCategory(filter.categories) .filterBySolved(filter.solved) - .filterByFavorite(filter.liked) + .filterByLiked(filter.liked) private fun filterBySearchQuery(query: SearchQuery): Questions { if (query.isEmpty()) return this @@ -40,9 +40,9 @@ data class Questions( return copy(values = values.filter { it.isSolved }) } - private fun filterByFavorite(showFavoritesOnly: Boolean): Questions { - if (!showFavoritesOnly) return this - return copy(values = values.filter { it.isFavorite }) + private fun filterByLiked(showLikedOnly: Boolean): Questions { + if (!showLikedOnly) return this + return copy(values = values.filter { it.isLiked }) } fun toList(): List = values.toList() From b6475f35e951d76dcf288291cf4019c38761f2ea Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 20:14:47 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=EC=A7=88=EB=AC=B8=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EA=B8=B0=EB=8A=A5=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuestionRepository에 toggleQuestionLike 메서드 추가 - RemoteQuestionDataSource에 addLike, removeLike 메서드 추가 - Supabase favorites 테이블 직접 insert/delete로 구현 - LikeRequest 모델 추가하여 좋아요 요청 직렬화 - fetchQuestionsByCategory, searchQuestions 메서드 제거 (클라이언트 필터링으로 대체) 🤖 Generated with [Firebender](https://firebender.com) Co-Authored-By: Firebender --- .../remote/DefaultRemoteQuestionDataSource.kt | 45 +++++++++---------- .../remote/RemoteQuestionDataSource.kt | 4 +- .../droidmorning/data/model/LikeRequest.kt | 12 +++++ .../repository/DefaultQuestionRepository.kt | 26 +++++------ .../domain/repository/QuestionRepository.kt | 8 ++-- 5 files changed, 50 insertions(+), 45 deletions(-) create mode 100644 data/src/commonMain/kotlin/com/peto/droidmorning/data/model/LikeRequest.kt diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt index 22eb9dd..ff1b7b6 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt @@ -1,51 +1,50 @@ package com.peto.droidmorning.data.datasource.question.remote +import com.peto.droidmorning.data.model.LikeRequest import com.peto.droidmorning.data.model.QuestionResponse import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.postgrest.Postgrest -import io.github.jan.supabase.postgrest.query.Columns -import io.github.jan.supabase.postgrest.query.Order -import io.github.jan.supabase.postgrest.query.filter.TextSearchType import io.github.jan.supabase.postgrest.rpc class DefaultRemoteQuestionDataSource( private val postgrest: Postgrest, - private val auth: Auth + auth: Auth, ) : RemoteQuestionDataSource { + private val uid: String = + auth.currentSessionOrNull()?.user?.id + ?: throw IllegalStateException("User not logged in") + override suspend fun fetchQuestions(): List { - val uid = auth.currentSessionOrNull()?.user?.id ?: return emptyList() val params = mapOf(RPC_FETCH_QUESTIONS_PARAM_NAME to uid) return postgrest .rpc(RPC_FETCH_QUESTIONS, params) .decodeList() } - override suspend fun fetchQuestionsByCategory(category: String): List = + override suspend fun addLike(questionId: Long) { + val request = LikeRequest(uid, questionId) postgrest - .from(TABLE_NAME) - .select(columns = Columns.ALL) { - filter { - eq(CATEGORY_COLUMN, category) - } - order(column = ORDER_BY_CREATED_AT, order = Order.DESCENDING) - }.decodeList() + .from(FAVORITES_TABLE) + .insert(request) + } - override suspend fun searchQuestions(query: String): List = + override suspend fun removeLike(questionId: Long) { postgrest - .from(TABLE_NAME) - .select(columns = Columns.ALL) { + .from(FAVORITES_TABLE) + .delete { filter { - textSearch(FTS_COLUMN, query, TextSearchType.WEBSEARCH) + eq(USER_ID_COLUMN, uid) + eq(QUESTION_ID_COLUMN, questionId) } - order(column = ORDER_BY_CREATED_AT, order = Order.DESCENDING) - }.decodeList() + } + } companion object { private const val RPC_FETCH_QUESTIONS = "fetch_questions" private const val RPC_FETCH_QUESTIONS_PARAM_NAME = "uid" - private const val TABLE_NAME = "questions" - private const val CATEGORY_COLUMN = "category" - private const val FTS_COLUMN = "fts" - private const val ORDER_BY_CREATED_AT = "created_at" + + private const val FAVORITES_TABLE = "favorites" + private const val USER_ID_COLUMN = "user_id" + private const val QUESTION_ID_COLUMN = "question_id" } } diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/RemoteQuestionDataSource.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/RemoteQuestionDataSource.kt index 6a6ed26..7e52b8a 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/RemoteQuestionDataSource.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/RemoteQuestionDataSource.kt @@ -5,7 +5,7 @@ import com.peto.droidmorning.data.model.QuestionResponse interface RemoteQuestionDataSource { suspend fun fetchQuestions(): List - suspend fun fetchQuestionsByCategory(category: String): List + suspend fun addLike(questionId: Long) - suspend fun searchQuestions(query: String): List + suspend fun removeLike(questionId: Long) } diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/LikeRequest.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/LikeRequest.kt new file mode 100644 index 0000000..6104014 --- /dev/null +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/model/LikeRequest.kt @@ -0,0 +1,12 @@ +package com.peto.droidmorning.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LikeRequest( + @SerialName("user_id") + val userId: String, + @SerialName("question_id") + val questionId: Long, +) diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepository.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepository.kt index 9ba7808..e453901 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepository.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepository.kt @@ -1,7 +1,6 @@ package com.peto.droidmorning.data.repository import com.peto.droidmorning.data.datasource.question.remote.RemoteQuestionDataSource -import com.peto.droidmorning.domain.model.Category import com.peto.droidmorning.domain.model.Questions import com.peto.droidmorning.domain.repository.QuestionRepository @@ -17,21 +16,16 @@ class DefaultQuestionRepository( Questions(result) } - override suspend fun fetchQuestionsByCategory(category: Category): Result = + override suspend fun toggleQuestionLike( + questionId: Long, + isCurrentlyLiked: Boolean, + ): Result = runCatching { - val result = - remoteQuestionDataSource - .fetchQuestionsByCategory(category.name) - .map { response -> response.toDomain() } - Questions(result) - } - - override suspend fun searchQuestions(query: String): Result = - runCatching { - val result = - remoteQuestionDataSource - .searchQuestions(query) - .map { response -> response.toDomain() } - Questions(result) + if (isCurrentlyLiked) { + remoteQuestionDataSource.removeLike(questionId) + } else { + remoteQuestionDataSource.addLike(questionId) + } + true } } diff --git a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/repository/QuestionRepository.kt b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/repository/QuestionRepository.kt index 961eaa8..d8760ab 100644 --- a/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/repository/QuestionRepository.kt +++ b/domain/src/commonMain/kotlin/com/peto/droidmorning/domain/repository/QuestionRepository.kt @@ -1,12 +1,12 @@ package com.peto.droidmorning.domain.repository -import com.peto.droidmorning.domain.model.Category import com.peto.droidmorning.domain.model.Questions interface QuestionRepository { suspend fun fetchQuestions(): Result - suspend fun fetchQuestionsByCategory(category: Category): Result - - suspend fun searchQuestions(query: String): Result + suspend fun toggleQuestionLike( + questionId: Long, + isCurrentlyLiked: Boolean, + ): Result } From 9399ba217a0397ff65695897f4937a1560e1701c Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 20:14:56 +0900 Subject: [PATCH 5/8] =?UTF-8?q?test:=20=EC=A7=88=EB=AC=B8=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FakeRemoteQuestionDataSource에 좋아��� 상태 추적 기능 추가 - toggleQuestionLike 테스트 케이스 2개 추가 (추가/제거) - fetchQuestionsByCategory, searchQuestions 테스트 제거 🤖 Generated with [Firebender](https://firebender.com) Co-Authored-By: Firebender --- .../data/fake/FakeRemoteQuestionDataSource.kt | 17 ++- .../DefaultQuestionRepositoryTest.kt | 131 +++--------------- 2 files changed, 30 insertions(+), 118 deletions(-) diff --git a/data/src/commonTest/kotlin/com/peto/droidmorning/data/fake/FakeRemoteQuestionDataSource.kt b/data/src/commonTest/kotlin/com/peto/droidmorning/data/fake/FakeRemoteQuestionDataSource.kt index 24cab77..33df236 100644 --- a/data/src/commonTest/kotlin/com/peto/droidmorning/data/fake/FakeRemoteQuestionDataSource.kt +++ b/data/src/commonTest/kotlin/com/peto/droidmorning/data/fake/FakeRemoteQuestionDataSource.kt @@ -6,10 +6,21 @@ import com.peto.droidmorning.data.model.QuestionResponse class FakeRemoteQuestionDataSource( private val questions: List, ) : RemoteQuestionDataSource { + private val likedQuestions = mutableSetOf() + override suspend fun fetchQuestions(): List = questions - override suspend fun fetchQuestionsByCategory(category: String): List = questions.filter { it.category == category } + override suspend fun addLike(questionId: Long) { + likedQuestions.add(questionId) + } + + override suspend fun removeLike(questionId: Long) { + likedQuestions.remove(questionId) + } + + fun isLiked(questionId: Long): Boolean = likedQuestions.contains(questionId) - override suspend fun searchQuestions(query: String): List = - questions.filter { it.title.contains(query, ignoreCase = true) } + fun clearLikes() { + likedQuestions.clear() + } } diff --git a/data/src/commonTest/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepositoryTest.kt b/data/src/commonTest/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepositoryTest.kt index 5c507b7..89247d8 100644 --- a/data/src/commonTest/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepositoryTest.kt +++ b/data/src/commonTest/kotlin/com/peto/droidmorning/data/repository/DefaultQuestionRepositoryTest.kt @@ -2,7 +2,6 @@ package com.peto.droidmorning.data.repository import com.peto.droidmorning.data.fake.FakeRemoteQuestionDataSource import com.peto.droidmorning.data.fixture.QuestionResponseFixture -import com.peto.droidmorning.domain.model.Category import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -28,139 +27,41 @@ class DefaultQuestionRepositoryTest { } @Test - fun `fetchQuestionsByCategory Kotlin은 Kotlin 카테고리만 반환한다`() = + fun `toggleQuestionLike는 좋아요가 추가되면 true를 반환한다`() = runTest { // given - val responses = - listOf( - QuestionResponseFixture.questionResponse( - id = 1L, - title = "match-${Category.Kotlin.name}", - category = Category.Kotlin, - ), - QuestionResponseFixture.questionResponse( - id = 2L, - title = "other-${Category.Android.name}", - category = Category.Android, - ), - ) - val fakeDataSource = FakeRemoteQuestionDataSource(questions = responses) - val repository = DefaultQuestionRepository(fakeDataSource) - - // when - val result = repository.fetchQuestionsByCategory(Category.Kotlin) - - // then - val questions = result.getOrThrow() - assertEquals(1, questions.size) - assertEquals(Category.Kotlin, questions.toList().first().category) - } - - @Test - fun `fetchQuestionsByCategory Coroutine은 Coroutine 카테고리만 반환한다`() = - runTest { - // given - val responses = - listOf( - QuestionResponseFixture.questionResponse( - id = 1L, - title = "match-${Category.Coroutine.name}", - category = Category.Coroutine, - ), - QuestionResponseFixture.questionResponse( - id = 2L, - title = "other-${Category.Compose.name}", - category = Category.Compose, - ), - ) - val fakeDataSource = FakeRemoteQuestionDataSource(questions = responses) - val repository = DefaultQuestionRepository(fakeDataSource) - - // when - val result = repository.fetchQuestionsByCategory(Category.Coroutine) - - // then - val questions = result.getOrThrow() - assertEquals(1, questions.size) - assertEquals(Category.Coroutine, questions.toList().first().category) - } - - @Test - fun `fetchQuestionsByCategory Android는 Android 카테고리만 반환한다`() = - runTest { - // given - val responses = - listOf( - QuestionResponseFixture.questionResponse( - id = 1L, - title = "match-${Category.Android.name}", - category = Category.Android, - ), - QuestionResponseFixture.questionResponse( - id = 2L, - title = "other-${Category.Kotlin.name}", - category = Category.Kotlin, - ), - ) + val responses = QuestionResponseFixture.questionResponseList() val fakeDataSource = FakeRemoteQuestionDataSource(questions = responses) val repository = DefaultQuestionRepository(fakeDataSource) + val questionId = 1L // when - val result = repository.fetchQuestionsByCategory(Category.Android) + val result = repository.toggleQuestionLike(questionId, isCurrentlyLiked = false) // then - val questions = result.getOrThrow() - assertEquals(1, questions.size) - assertEquals(Category.Android, questions.toList().first().category) + assertTrue(result.isSuccess) + assertTrue(result.getOrThrow()) + assertTrue(fakeDataSource.isLiked(questionId)) } @Test - fun `fetchQuestionsByCategory Compose는 Compose 카테고리만 반환한다`() = + fun `toggleQuestionLike는 좋아요가 제거되면 true를 반환한다`() = runTest { // given - val responses = - listOf( - QuestionResponseFixture.questionResponse( - id = 1L, - title = "match-${Category.Compose.name}", - category = Category.Compose, - ), - QuestionResponseFixture.questionResponse( - id = 2L, - title = "other-${Category.Coroutine.name}", - category = Category.Coroutine, - ), - ) + val responses = QuestionResponseFixture.questionResponseList() val fakeDataSource = FakeRemoteQuestionDataSource(questions = responses) val repository = DefaultQuestionRepository(fakeDataSource) + val questionId = 1L - // when - val result = repository.fetchQuestionsByCategory(Category.Compose) - - // then - val questions = result.getOrThrow() - assertEquals(1, questions.size) - assertEquals(Category.Compose, questions.toList().first().category) - } - - @Test - fun `searchQuestions는 query가 포함된 질문만 반환한다`() = - runTest { - // given - val responses = - listOf( - QuestionResponseFixture.questionResponse(title = "Kotlin Coroutines"), - QuestionResponseFixture.questionResponse(title = "Swift Concurrency"), - ) - val fakeDataSource = FakeRemoteQuestionDataSource(questions = responses) - val repository = DefaultQuestionRepository(fakeDataSource) + // 먼저 좋아요 추가 + fakeDataSource.addLike(questionId) // when - val result = repository.searchQuestions("Kotlin") + val result = repository.toggleQuestionLike(questionId, isCurrentlyLiked = true) // then - val questions = result.getOrThrow() - assertEquals(1, questions.size) - assertEquals("Kotlin Coroutines", questions.toList().first().title) + assertTrue(result.isSuccess) + assertTrue(result.getOrThrow()) + assertTrue(!fakeDataSource.isLiked(questionId)) } } From dafb6213a4640ef06d15b79a152c9ea65055168e Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 20:15:06 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=EC=A7=88=EB=AC=B8=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20UI=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuestionViewModel에 onLikeToggle 메서드 구현 - 낙관적 업데이트(Optimistic Update) 적용하여 즉각적인 UI 반응 - 서버 요청 실패 시 이전 상태로 롤백 - QuestionUiState에 toggleQuestionLike 메서드 추가하여 특정 질문의 좋아요 상태 토글 🤖 Generated with [Firebender](https://firebender.com) Co-Authored-By: Firebender --- .../droidmorning/question/vm/QuestionUiState.kt | 12 ++++++++++++ .../question/vm/QuestionViewModel.kt | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt index 63ae382..9fc4010 100644 --- a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt +++ b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionUiState.kt @@ -52,6 +52,18 @@ data class QuestionUiState( fun clearLikedFilter(): QuestionUiState = copy(filter = filter.clearLikedFilter()) + fun toggleQuestionLike(questionId: Long): QuestionUiState { + val updatedList = + allQuestions.toList().map { question -> + if (question.id == questionId) { + question.copy(isLiked = !question.isLiked) + } else { + question + } + } + return copy(allQuestions = Questions(updatedList)) + } + fun loading(isLoading: Boolean): QuestionUiState = copy(isLoading = isLoading) fun filtering(): QuestionUiState = copy(isFiltering = true) diff --git a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt index 8e1bfd9..72e0fbe 100644 --- a/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/peto/droidmorning/question/vm/QuestionViewModel.kt @@ -83,7 +83,21 @@ class QuestionViewModel( } fun onLikeToggle(questionId: Long) { - // TODO: Implement like toggle + viewModelScope.launch { + val currentQuestion = _uiState.value.filteredQuestions.find { it.id == questionId } ?: return@launch + val isCurrentlyLiked = currentQuestion.isLiked + + _uiState.update { it.toggleQuestionLike(questionId) } + + questionRepository + .toggleQuestionLike(questionId, isCurrentlyLiked) + .onFailure { + _uiState.update { state -> + state.toggleQuestionLike(questionId) + } + sendUiEvent(QuestionUiEvent.ShowError) + } + } } private fun loadQuestions() { From 6e9da08d0e607d5c16718b15a2209b6a171769e0 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 20:16:48 +0900 Subject: [PATCH 7/8] =?UTF-8?q?test:=20Domain=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EC=84=9C=20favorite=20=EC=9A=A9=EC=96=B4?= =?UTF-8?q?=EB=A5=BC=20like=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FilterTest: applyFavoritesFilter → applyLikedFilter - FilterTest: clearFavoritesFilter → clearLikedFilter - QuestionTest, QuestionsTest: isFavorite → isLiked 🤖 Generated with [Firebender](https://firebender.com) Co-Authored-By: Firebender --- .../com/peto/droidmorning/domain/model/FilterTest.kt | 4 ++-- .../peto/droidmorning/domain/model/QuestionTest.kt | 4 ++-- .../peto/droidmorning/domain/model/QuestionsTest.kt | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/FilterTest.kt b/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/FilterTest.kt index 0a504f8..bd3abd6 100644 --- a/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/FilterTest.kt +++ b/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/FilterTest.kt @@ -151,7 +151,7 @@ class FilterTest { // Given // When - val result = emptyFilter.applyFavoritesFilter() + val result = emptyFilter.applyLikedFilter() // Then assertTrue(result.liked) @@ -162,7 +162,7 @@ class FilterTest { // Given // When - val result = filterWithFavorites.clearFavoritesFilter() + val result = filterWithFavorites.clearLikedFilter() // Then assertFalse(result.liked) diff --git a/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionTest.kt b/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionTest.kt index 5d34fda..3b9026f 100644 --- a/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionTest.kt +++ b/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionTest.kt @@ -20,7 +20,7 @@ class QuestionTest { category: Category = Category.Kotlin, sourceUrl: String = "https://example.com", isSolved: Boolean = false, - isFavorite: Boolean = false, + isLiked: Boolean = false, ) = Question( id = id, title = title, @@ -29,7 +29,7 @@ class QuestionTest { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = isSolved, - isFavorite = isFavorite, + isLiked = isLiked, ) @Test diff --git a/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionsTest.kt b/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionsTest.kt index 5a9d5a8..c4f8487 100644 --- a/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionsTest.kt +++ b/domain/src/commonTest/kotlin/com/peto/droidmorning/domain/model/QuestionsTest.kt @@ -49,9 +49,9 @@ class QuestionsTest { mixedFavoriteQuestions = Questions( listOf( - createQuestion(id = 1, isFavorite = true), - createQuestion(id = 2, isFavorite = false), - createQuestion(id = 3, isFavorite = true), + createQuestion(id = 1, isLiked = true), + createQuestion(id = 2, isLiked = false), + createQuestion(id = 3, isLiked = true), ), ) } @@ -61,7 +61,7 @@ class QuestionsTest { title: String = "Test Question", category: Category = Category.Kotlin, isSolved: Boolean = false, - isFavorite: Boolean = false, + isLiked: Boolean = false, ) = Question( id = id, title = title, @@ -70,7 +70,7 @@ class QuestionsTest { createdAt = Instant.fromEpochMilliseconds(0), updatedAt = Instant.fromEpochMilliseconds(0), isSolved = isSolved, - isFavorite = isFavorite, + isLiked = isLiked, ) @Test @@ -182,7 +182,7 @@ class QuestionsTest { // Then assertAll( { assertEquals(2, result.size) }, - { assertTrue(result.toList().all { it.isFavorite }) }, + { assertTrue(result.toList().all { it.isLiked }) }, ) } From 21d88702ffc6a71254e1ce8d9dcc1c35258c146e Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Thu, 15 Jan 2026 20:32:44 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20uid=EB=A5=BC=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=83=81=ED=83=9C=20=EC=9D=BC=EA=B4=80=EC=84=B1=20?= =?UTF-8?q?=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `DefaultRemoteQuestionDataSource` 클래스 내에서 사용자의 UID를 가져오는 방식을 프로퍼티에서 메서드로 변경했습니다. 기존에는 클래스 초기화 시점에 UID를 한 번만 가져와 프로퍼티에 저장했기 때문에, 사용자의 로그인 상태가 변경될 경우 오래된 UID 값을 사용할 수 있는 문제가 있었습니다. 이제 `uid()` 메서드를 통해 각 함수가 호출되는 시점의 최신 사용자 세션에서 UID를 가져오도록 수정하여, 데이터 요청 시 항상 정확하고 유효한 사용자 ID를 사용하도록 보장합니다. --- .../question/remote/DefaultRemoteQuestionDataSource.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt index ff1b7b6..9af92c0 100644 --- a/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt +++ b/data/src/commonMain/kotlin/com/peto/droidmorning/data/datasource/question/remote/DefaultRemoteQuestionDataSource.kt @@ -8,21 +8,21 @@ import io.github.jan.supabase.postgrest.rpc class DefaultRemoteQuestionDataSource( private val postgrest: Postgrest, - auth: Auth, + private val auth: Auth, ) : RemoteQuestionDataSource { - private val uid: String = + private fun uid(): String = auth.currentSessionOrNull()?.user?.id ?: throw IllegalStateException("User not logged in") override suspend fun fetchQuestions(): List { - val params = mapOf(RPC_FETCH_QUESTIONS_PARAM_NAME to uid) + val params = mapOf(RPC_FETCH_QUESTIONS_PARAM_NAME to uid()) return postgrest .rpc(RPC_FETCH_QUESTIONS, params) .decodeList() } override suspend fun addLike(questionId: Long) { - val request = LikeRequest(uid, questionId) + val request = LikeRequest(uid(), questionId) postgrest .from(FAVORITES_TABLE) .insert(request) @@ -33,7 +33,7 @@ class DefaultRemoteQuestionDataSource( .from(FAVORITES_TABLE) .delete { filter { - eq(USER_ID_COLUMN, uid) + eq(USER_ID_COLUMN, uid()) eq(QUESTION_ID_COLUMN, questionId) } }