From 9b6fc1dd01fbf495edbf96bbdfdaf2e32ffc169c Mon Sep 17 00:00:00 2001 From: sanjeok77 Date: Thu, 5 Mar 2026 04:13:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HotDeal, HotDealEntity에 isFavorite 필드 추가 - HotDealDao에 즐겨찾기 관련 쿼리 메서드 추가 - 데이터베이스 마이그레이션 (v3 -> v4) 추가 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- .../alarm/data/local/db/AppDatabase.kt | 22 +++++++++++----- .../alarm/data/local/db/dao/HotDealDao.kt | 26 +++++++++++++++++-- .../data/local/db/entity/HotDealEntity.kt | 17 +++++++----- .../com/hotdeal/alarm/di/DatabaseModule.kt | 9 ++++--- .../com/hotdeal/alarm/domain/model/HotDeal.kt | 7 ++--- 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/hotdeal/alarm/data/local/db/AppDatabase.kt b/app/src/main/java/com/hotdeal/alarm/data/local/db/AppDatabase.kt index cd7f913..13c958e 100644 --- a/app/src/main/java/com/hotdeal/alarm/data/local/db/AppDatabase.kt +++ b/app/src/main/java/com/hotdeal/alarm/data/local/db/AppDatabase.kt @@ -21,7 +21,7 @@ import com.hotdeal.alarm.data.local.db.entity.SiteConfigEntity SiteConfigEntity::class, KeywordEntity::class ], - version = 3, + version = 4, exportSchema = false ) @TypeConverters(Converters::class) @@ -46,11 +46,19 @@ abstract class AppDatabase : RoomDatabase() { /** * Migration 2 -> 3: 인덱스 추가 */ - val MIGRATION_2_3 = object : Migration(2, 3) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE INDEX IF NOT EXISTS index_hot_deals_siteName_boardName ON hot_deals(siteName, boardName)") - db.execSQL("CREATE INDEX IF NOT EXISTS index_hot_deals_createdAt ON hot_deals(createdAt)") - } - } + val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE INDEX IF NOT EXISTS index_hot_deals_siteName_boardName ON hot_deals(siteName, boardName)") + db.execSQL("CREATE INDEX IF NOT EXISTS index_hot_deals_createdAt ON hot_deals(createdAt)") + } + } + + /** + * Migration 3 -> 4: 즐겨찾기 필드 추가 + */ + val MIGRATION_3_4 = object : Migration(3, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE hot_deals ADD COLUMN isFavorite INTEGER NOT NULL DEFAULT 0") + } } } diff --git a/app/src/main/java/com/hotdeal/alarm/data/local/db/dao/HotDealDao.kt b/app/src/main/java/com/hotdeal/alarm/data/local/db/dao/HotDealDao.kt index 2ce4b1d..1020d80 100644 --- a/app/src/main/java/com/hotdeal/alarm/data/local/db/dao/HotDealDao.kt +++ b/app/src/main/java/com/hotdeal/alarm/data/local/db/dao/HotDealDao.kt @@ -97,6 +97,28 @@ interface HotDealDao { /** * 제목으로 검색 */ - @Query("SELECT * FROM hot_deals WHERE title LIKE '%' || :query || '%' ORDER BY createdAt DESC") - fun searchDeals(query: String): Flow> + @Query("SELECT * FROM hot_deals WHERE title LIKE '%' || :query || '%' ORDER BY createdAt DESC") + fun searchDeals(query: String): Flow> + + // ========================= + // 즐겨찾기 관련 메서드 + // ========================= + + /** + * 즐겨찾기 핫딜 조회 + */ + @Query("SELECT * FROM hot_deals WHERE isFavorite = 1 ORDER BY createdAt DESC") + fun observeFavoriteDeals(): Flow> + + /** + * 즐겨찾기 상태 토글 + */ + @Query("UPDATE hot_deals SET isFavorite = NOT isFavorite WHERE id = :id") + suspend fun toggleFavorite(id: String) + + /** + * 즐겨찾기 상태 설정 + */ + @Query("UPDATE hot_deals SET isFavorite = :isFavorite WHERE id = :id") + suspend fun setFavorite(id: String, isFavorite: Boolean) } diff --git a/app/src/main/java/com/hotdeal/alarm/data/local/db/entity/HotDealEntity.kt b/app/src/main/java/com/hotdeal/alarm/data/local/db/entity/HotDealEntity.kt index ce1d9eb..a68a7b3 100644 --- a/app/src/main/java/com/hotdeal/alarm/data/local/db/entity/HotDealEntity.kt +++ b/app/src/main/java/com/hotdeal/alarm/data/local/db/entity/HotDealEntity.kt @@ -23,9 +23,10 @@ data class HotDealEntity( val title: String, val url: String, val mallUrl: String?, - val createdAt: Long, - val isNotified: Boolean = false, - val isKeywordMatch: Boolean = false + val createdAt: Long, + val isNotified: Boolean = false, + val isKeywordMatch: Boolean = false, + val isFavorite: Boolean = false // 즐겨찾기 여부 ) { /** * Domain 모델로 변환 @@ -39,8 +40,9 @@ data class HotDealEntity( url = url, mallUrl = mallUrl, createdAt = createdAt, - isNotified = isNotified, - isKeywordMatch = isKeywordMatch + isNotified = isNotified, + isKeywordMatch = isKeywordMatch, + isFavorite = isFavorite ) } @@ -57,8 +59,9 @@ data class HotDealEntity( url = domain.url, mallUrl = domain.mallUrl, createdAt = domain.createdAt, - isNotified = domain.isNotified, - isKeywordMatch = domain.isKeywordMatch + isNotified = domain.isNotified, + isKeywordMatch = domain.isKeywordMatch, + isFavorite = domain.isFavorite ) } } diff --git a/app/src/main/java/com/hotdeal/alarm/di/DatabaseModule.kt b/app/src/main/java/com/hotdeal/alarm/di/DatabaseModule.kt index e0d01fc..116eccf 100644 --- a/app/src/main/java/com/hotdeal/alarm/di/DatabaseModule.kt +++ b/app/src/main/java/com/hotdeal/alarm/di/DatabaseModule.kt @@ -31,10 +31,11 @@ object DatabaseModule { AppDatabase::class.java, AppDatabase.DATABASE_NAME ) - .addMigrations( - AppDatabase.MIGRATION_1_2, - AppDatabase.MIGRATION_2_3 - ) + .addMigrations( + AppDatabase.MIGRATION_1_2, + AppDatabase.MIGRATION_2_3, + AppDatabase.MIGRATION_3_4 + ) .fallbackToDestructiveMigration() .build() } diff --git a/app/src/main/java/com/hotdeal/alarm/domain/model/HotDeal.kt b/app/src/main/java/com/hotdeal/alarm/domain/model/HotDeal.kt index d33438b..f5385b0 100644 --- a/app/src/main/java/com/hotdeal/alarm/domain/model/HotDeal.kt +++ b/app/src/main/java/com/hotdeal/alarm/domain/model/HotDeal.kt @@ -10,9 +10,10 @@ data class HotDeal( val title: String, // 게시글 제목 val url: String, // 게시글 URL val mallUrl: String? = null, // 쇼핑몰 URL (추출된 경우) - val createdAt: Long, // 수집 시간 (timestamp) - val isNotified: Boolean = false, - val isKeywordMatch: Boolean = false + val createdAt: Long, // 수집 시간 (timestamp) + val isNotified: Boolean = false, + val isKeywordMatch: Boolean = false, + val isFavorite: Boolean = false // 즐겨찾기 여부 ) { /** * 사이트 타입 반환