feat: add DB migration v3→v4 with new LocalPost columns for email, media, files

This commit is contained in:
Paweł Orzech 2026-03-20 00:21:28 +01:00
parent 0891013df6
commit 8326d06861
4 changed files with 50 additions and 11 deletions

View file

@ -9,7 +9,7 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.swoosh.microblog.data.model.LocalPost
@Database(entities = [LocalPost::class], version = 3, exportSchema = false)
@Database(entities = [LocalPost::class], version = 4, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
@ -42,6 +42,25 @@ abstract class AppDatabase : RoomDatabase() {
override fun migrate(db: SupportSQLiteDatabase) = addColumnsIfMissing(db)
}
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
val columns = listOf(
"ALTER TABLE local_posts ADD COLUMN emailOnly INTEGER NOT NULL DEFAULT 0",
"ALTER TABLE local_posts ADD COLUMN newsletterSlug TEXT DEFAULT NULL",
"ALTER TABLE local_posts ADD COLUMN videoUri TEXT DEFAULT NULL",
"ALTER TABLE local_posts ADD COLUMN uploadedVideoUrl TEXT DEFAULT NULL",
"ALTER TABLE local_posts ADD COLUMN audioUri TEXT DEFAULT NULL",
"ALTER TABLE local_posts ADD COLUMN uploadedAudioUrl TEXT DEFAULT NULL",
"ALTER TABLE local_posts ADD COLUMN fileUri TEXT DEFAULT NULL",
"ALTER TABLE local_posts ADD COLUMN uploadedFileUrl TEXT DEFAULT NULL",
"ALTER TABLE local_posts ADD COLUMN fileName TEXT DEFAULT NULL"
)
for (sql in columns) {
try { db.execSQL(sql) } catch (_: Exception) { }
}
}
}
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
@ -49,7 +68,7 @@ abstract class AppDatabase : RoomDatabase() {
AppDatabase::class.java,
"swoosh_database"
)
.addMigrations(MIGRATION_1_3, MIGRATION_2_3)
.addMigrations(MIGRATION_1_3, MIGRATION_2_3, MIGRATION_3_4)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance

View file

@ -11,13 +11,21 @@ class Converters {
fun fromPostStatus(value: PostStatus): String = value.name
@TypeConverter
fun toPostStatus(value: String): PostStatus = PostStatus.valueOf(value)
fun toPostStatus(value: String): PostStatus = try {
PostStatus.valueOf(value)
} catch (_: Exception) {
PostStatus.DRAFT
}
@TypeConverter
fun fromQueueStatus(value: QueueStatus): String = value.name
@TypeConverter
fun toQueueStatus(value: String): QueueStatus = QueueStatus.valueOf(value)
fun toQueueStatus(value: String): QueueStatus = try {
QueueStatus.valueOf(value)
} catch (_: Exception) {
QueueStatus.NONE
}
companion object {
private val gson = Gson()

View file

@ -89,7 +89,19 @@ data class LocalPost(
val tags: String = "[]",
val createdAt: Long = System.currentTimeMillis(),
val updatedAt: Long = System.currentTimeMillis(),
val queueStatus: QueueStatus = QueueStatus.NONE
val queueStatus: QueueStatus = QueueStatus.NONE,
// Phase 4b: email-only
val emailOnly: Boolean = false,
val newsletterSlug: String? = null,
// Phase 5: media
val videoUri: String? = null,
val uploadedVideoUrl: String? = null,
val audioUri: String? = null,
val uploadedAudioUrl: String? = null,
// Phase 6: file
val fileUri: String? = null,
val uploadedFileUrl: String? = null,
val fileName: String? = null
)
enum class PostStatus {

View file

@ -56,9 +56,9 @@ class ConvertersTest {
}
}
@Test(expected = IllegalArgumentException::class)
fun `toPostStatus throws on invalid string`() {
converters.toPostStatus("INVALID")
@Test
fun `toPostStatus returns DRAFT fallback on invalid string`() {
assertEquals(PostStatus.DRAFT, converters.toPostStatus("INVALID"))
}
// --- QueueStatus conversions ---
@ -112,9 +112,9 @@ class ConvertersTest {
}
}
@Test(expected = IllegalArgumentException::class)
fun `toQueueStatus throws on invalid string`() {
converters.toQueueStatus("NONEXISTENT")
@Test
fun `toQueueStatus returns NONE fallback on invalid string`() {
assertEquals(QueueStatus.NONE, converters.toQueueStatus("NONEXISTENT"))
}
// --- String list JSON serialization ---