feat: Add methods to retrieve all field and item IDs in DAO interfaces

This commit is contained in:
2025-10-25 15:54:31 +02:00
parent 36ae1f8c33
commit fe3f7ee529
4 changed files with 47 additions and 38 deletions
@@ -12,6 +12,9 @@ interface FieldDao {
@Query("SELECT * FROM fields WHERE id = :fieldId")
suspend fun getFieldById(fieldId: String): Field?
@Query("SELECT id FROM fields")
suspend fun getAllFieldIds(): List<String>
@Upsert
suspend fun insertField(field: Field)
@@ -17,6 +17,9 @@ interface ItemDao {
@Query("SELECT * FROM items WHERE id = :itemId")
suspend fun getItemById(itemId: String): Item?
@Query("SELECT id FROM items")
suspend fun getAllItemIds(): List<String>
@Upsert
suspend fun insertItem(item: Item)
@@ -7,6 +7,7 @@ import com.collabtable.app.data.database.CollabTableDatabase
import com.collabtable.app.utils.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import androidx.room.withTransaction
class SyncRepository(context: Context) {
private val database = CollabTableDatabase.getDatabase(context)
@@ -25,9 +26,8 @@ class SyncRepository(context: Context) {
try {
val lastSync = getLastSyncTimestamp()
val isInitialSync = lastSync == 0L
if (isInitialSync) {
Logger.i("Sync", "🔄 Starting initial sync with server")
Logger.i("Sync", "[SYNC] Starting initial sync with server")
}
// Gather local changes since last sync
@@ -36,19 +36,13 @@ class SyncRepository(context: Context) {
val localItems = database.itemDao().getItemsUpdatedSince(lastSync)
val localValues = database.itemValueDao().getValuesUpdatedSince(lastSync)
// Only log when sending data
if (localLists.isNotEmpty() || localFields.isNotEmpty() || localItems.isNotEmpty()) {
Logger.i("Sync", "⬆️ Sending to server:")
if (localLists.isNotEmpty()) {
Logger.i("Sync", " 📋 ${localLists.size} list(s)")
localLists.forEach { list ->
val action = if (list.isDeleted) "Deleted" else "Updated"
Logger.i("Sync", " $action: ${list.name}")
}
}
if (localFields.isNotEmpty()) Logger.i("Sync", " 🏷️ ${localFields.size} field(s)")
if (localItems.isNotEmpty()) Logger.i("Sync", " 📝 ${localItems.size} item(s)")
if (localValues.isNotEmpty()) Logger.i("Sync", " 💾 ${localValues.size} value(s)")
// Only log send when there are actual local changes
val localTotal = localLists.size + localFields.size + localItems.size + localValues.size
if (localTotal > 0) {
Logger.i(
"Sync",
"[OUT] Sending changes (lists=${localLists.size}, fields=${localFields.size}, items=${localItems.size}, values=${localValues.size})"
)
}
// Send to server and get updates
@@ -65,43 +59,52 @@ class SyncRepository(context: Context) {
if (response.isSuccessful) {
val syncResponse = response.body()!!
// Only log when receiving data
if (syncResponse.lists.isNotEmpty() || syncResponse.fields.isNotEmpty() ||
syncResponse.items.isNotEmpty() || syncResponse.itemValues.isNotEmpty()) {
Logger.i("Sync", "⬇️ Received from server:")
if (syncResponse.lists.isNotEmpty()) {
Logger.i("Sync", " 📋 ${syncResponse.lists.size} list(s)")
syncResponse.lists.forEach { list ->
if (!list.isDeleted) {
Logger.i("Sync", " ${list.name}")
}
}
}
if (syncResponse.fields.isNotEmpty()) Logger.i("Sync", " 🏷️ ${syncResponse.fields.size} field(s)")
if (syncResponse.items.isNotEmpty()) Logger.i("Sync", " 📝 ${syncResponse.items.size} item(s)")
if (syncResponse.itemValues.isNotEmpty()) Logger.i("Sync", " 💾 ${syncResponse.itemValues.size} value(s)")
// Only log receive when there are actual server changes
val inTotal = syncResponse.lists.size + syncResponse.fields.size + syncResponse.items.size + syncResponse.itemValues.size
if (inTotal > 0) {
Logger.i(
"Sync",
"[IN] Received changes (lists=${syncResponse.lists.size}, fields=${syncResponse.fields.size}, items=${syncResponse.items.size}, values=${syncResponse.itemValues.size})"
)
}
// Apply server changes to local database
// Apply server changes to local database atomically in correct order
database.withTransaction {
database.listDao().insertLists(syncResponse.lists)
database.fieldDao().insertFields(syncResponse.fields)
database.itemDao().insertItems(syncResponse.items)
database.itemValueDao().insertValues(syncResponse.itemValues)
// Filter item values to only those whose parents exist locally to avoid FK violations
val existingItemIds = database.itemDao().getAllItemIds().toSet()
val existingFieldIds = database.fieldDao().getAllFieldIds().toSet()
val filteredValues = syncResponse.itemValues.filter { v ->
v.itemId in existingItemIds && v.fieldId in existingFieldIds
}
if (filteredValues.size != syncResponse.itemValues.size) {
val dropped = syncResponse.itemValues.size - filteredValues.size
Logger.w("Sync", "Dropping $dropped item value(s) with missing parents to avoid FK errors")
}
if (filteredValues.isNotEmpty()) {
database.itemValueDao().insertValues(filteredValues)
}
}
// Update last sync timestamp
setLastSyncTimestamp(syncResponse.serverTimestamp)
if (isInitialSync) {
Logger.i("Sync", " Initial sync completed")
Logger.i("Sync", "[SYNC] Initial sync completed")
}
return@withContext Result.success(Unit)
} else {
Logger.e("Sync", " Sync failed: HTTP ${response.code()}")
Logger.e("Sync", "[ERROR] Sync failed: HTTP ${response.code()}")
return@withContext Result.failure(Exception("Sync failed: ${response.code()}"))
}
} catch (e: Exception) {
Logger.e("Sync", " Sync error: ${e.message}")
Logger.e("Sync", "[ERROR] Sync error: ${e.message}")
return@withContext Result.failure(e)
}
}
Binary file not shown.