diff --git a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt index f62ad44b..bc169ae5 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/AppDatabase.kt @@ -5,10 +5,12 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import com.fabledsword.minstrel.cache.db.dao.CachedAlbumDao import com.fabledsword.minstrel.cache.db.dao.CachedArtistDao +import com.fabledsword.minstrel.cache.db.dao.CachedLikeDao import com.fabledsword.minstrel.cache.db.dao.CachedTrackDao import com.fabledsword.minstrel.cache.db.dao.SyncMetadataDao import com.fabledsword.minstrel.cache.db.entities.CachedAlbumEntity import com.fabledsword.minstrel.cache.db.entities.CachedArtistEntity +import com.fabledsword.minstrel.cache.db.entities.CachedLikeEntity import com.fabledsword.minstrel.cache.db.entities.CachedTrackEntity import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity @@ -31,6 +33,7 @@ import com.fabledsword.minstrel.cache.db.entities.SyncMetadataEntity CachedArtistEntity::class, CachedAlbumEntity::class, CachedTrackEntity::class, + CachedLikeEntity::class, ], version = 1, exportSchema = true, @@ -41,4 +44,5 @@ abstract class AppDatabase : RoomDatabase() { abstract fun cachedArtistDao(): CachedArtistDao abstract fun cachedAlbumDao(): CachedAlbumDao abstract fun cachedTrackDao(): CachedTrackDao + abstract fun cachedLikeDao(): CachedLikeDao } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/dao/CachedLikeDao.kt b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/dao/CachedLikeDao.kt new file mode 100644 index 00000000..21b3d56c --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/dao/CachedLikeDao.kt @@ -0,0 +1,39 @@ +package com.fabledsword.minstrel.cache.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.fabledsword.minstrel.cache.db.entities.CachedLikeEntity +import kotlinx.coroutines.flow.Flow + +@Dao +interface CachedLikeDao { + /** All track IDs the user has liked. Audio-cache eviction uses this set. */ + @Query( + "SELECT entityId FROM cached_likes " + + "WHERE userId = :userId AND entityType = 'track'", + ) + fun observeLikedTrackIds(userId: String): Flow> + + @Query( + "SELECT entityId FROM cached_likes " + + "WHERE userId = :userId AND entityType = :entityType", + ) + fun observeLikedIdsOfType(userId: String, entityType: String): Flow> + + @Query( + "SELECT EXISTS(SELECT 1 FROM cached_likes " + + "WHERE userId = :userId AND entityType = :entityType AND entityId = :entityId)", + ) + fun observeIsLiked(userId: String, entityType: String, entityId: String): Flow + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun upsertAll(rows: List) + + @Query( + "DELETE FROM cached_likes " + + "WHERE userId = :userId AND entityType = :entityType AND entityId = :entityId", + ) + suspend fun delete(userId: String, entityType: String, entityId: String) +} diff --git a/android/app/src/main/java/com/fabledsword/minstrel/cache/db/entities/CachedLikeEntity.kt b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/entities/CachedLikeEntity.kt new file mode 100644 index 00000000..6f3af1e2 --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/cache/db/entities/CachedLikeEntity.kt @@ -0,0 +1,26 @@ +package com.fabledsword.minstrel.cache.db.entities + +import androidx.room.Entity +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant + +/** + * Like membership row. Mirrors `flutter_client/lib/cache/db.dart`'s + * `CachedLikes` Drift table. Composite primary key — one user may + * independently like a track AND its album AND its artist; rows are + * disambiguated by the (userId, entityType, entityId) triple. + * + * `entityType` is one of "track" | "album" | "artist". Stored as a + * plain string for parity with the Drift schema and the server's wire + * format; an enum doesn't earn its keep here. + */ +@Entity( + tableName = "cached_likes", + primaryKeys = ["userId", "entityType", "entityId"], +) +data class CachedLikeEntity( + val userId: String, + val entityType: String, + val entityId: String, + val likedAt: Instant = Clock.System.now(), +)