feat: Add methods to retrieve all field and item IDs in DAO interfaces
This commit is contained in:
@@ -12,6 +12,9 @@ interface FieldDao {
|
|||||||
@Query("SELECT * FROM fields WHERE id = :fieldId")
|
@Query("SELECT * FROM fields WHERE id = :fieldId")
|
||||||
suspend fun getFieldById(fieldId: String): Field?
|
suspend fun getFieldById(fieldId: String): Field?
|
||||||
|
|
||||||
|
@Query("SELECT id FROM fields")
|
||||||
|
suspend fun getAllFieldIds(): List<String>
|
||||||
|
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun insertField(field: Field)
|
suspend fun insertField(field: Field)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ interface ItemDao {
|
|||||||
@Query("SELECT * FROM items WHERE id = :itemId")
|
@Query("SELECT * FROM items WHERE id = :itemId")
|
||||||
suspend fun getItemById(itemId: String): Item?
|
suspend fun getItemById(itemId: String): Item?
|
||||||
|
|
||||||
|
@Query("SELECT id FROM items")
|
||||||
|
suspend fun getAllItemIds(): List<String>
|
||||||
|
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun insertItem(item: Item)
|
suspend fun insertItem(item: Item)
|
||||||
|
|
||||||
|
|||||||
+41
-38
@@ -7,6 +7,7 @@ import com.collabtable.app.data.database.CollabTableDatabase
|
|||||||
import com.collabtable.app.utils.Logger
|
import com.collabtable.app.utils.Logger
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import androidx.room.withTransaction
|
||||||
|
|
||||||
class SyncRepository(context: Context) {
|
class SyncRepository(context: Context) {
|
||||||
private val database = CollabTableDatabase.getDatabase(context)
|
private val database = CollabTableDatabase.getDatabase(context)
|
||||||
@@ -25,9 +26,8 @@ class SyncRepository(context: Context) {
|
|||||||
try {
|
try {
|
||||||
val lastSync = getLastSyncTimestamp()
|
val lastSync = getLastSyncTimestamp()
|
||||||
val isInitialSync = lastSync == 0L
|
val isInitialSync = lastSync == 0L
|
||||||
|
|
||||||
if (isInitialSync) {
|
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
|
// Gather local changes since last sync
|
||||||
@@ -36,19 +36,13 @@ class SyncRepository(context: Context) {
|
|||||||
val localItems = database.itemDao().getItemsUpdatedSince(lastSync)
|
val localItems = database.itemDao().getItemsUpdatedSince(lastSync)
|
||||||
val localValues = database.itemValueDao().getValuesUpdatedSince(lastSync)
|
val localValues = database.itemValueDao().getValuesUpdatedSince(lastSync)
|
||||||
|
|
||||||
// Only log when sending data
|
// Only log send when there are actual local changes
|
||||||
if (localLists.isNotEmpty() || localFields.isNotEmpty() || localItems.isNotEmpty()) {
|
val localTotal = localLists.size + localFields.size + localItems.size + localValues.size
|
||||||
Logger.i("Sync", "⬆️ Sending to server:")
|
if (localTotal > 0) {
|
||||||
if (localLists.isNotEmpty()) {
|
Logger.i(
|
||||||
Logger.i("Sync", " 📋 ${localLists.size} list(s)")
|
"Sync",
|
||||||
localLists.forEach { list ->
|
"[OUT] Sending changes (lists=${localLists.size}, fields=${localFields.size}, items=${localItems.size}, values=${localValues.size})"
|
||||||
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)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to server and get updates
|
// Send to server and get updates
|
||||||
@@ -65,43 +59,52 @@ class SyncRepository(context: Context) {
|
|||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val syncResponse = response.body()!!
|
val syncResponse = response.body()!!
|
||||||
|
|
||||||
// Only log when receiving data
|
// Only log receive when there are actual server changes
|
||||||
if (syncResponse.lists.isNotEmpty() || syncResponse.fields.isNotEmpty() ||
|
val inTotal = syncResponse.lists.size + syncResponse.fields.size + syncResponse.items.size + syncResponse.itemValues.size
|
||||||
syncResponse.items.isNotEmpty() || syncResponse.itemValues.isNotEmpty()) {
|
if (inTotal > 0) {
|
||||||
Logger.i("Sync", "⬇️ Received from server:")
|
Logger.i(
|
||||||
if (syncResponse.lists.isNotEmpty()) {
|
"Sync",
|
||||||
Logger.i("Sync", " 📋 ${syncResponse.lists.size} list(s)")
|
"[IN] Received changes (lists=${syncResponse.lists.size}, fields=${syncResponse.fields.size}, items=${syncResponse.items.size}, values=${syncResponse.itemValues.size})"
|
||||||
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)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply server changes to local database
|
// Apply server changes to local database atomically in correct order
|
||||||
database.listDao().insertLists(syncResponse.lists)
|
database.withTransaction {
|
||||||
database.fieldDao().insertFields(syncResponse.fields)
|
database.listDao().insertLists(syncResponse.lists)
|
||||||
database.itemDao().insertItems(syncResponse.items)
|
database.fieldDao().insertFields(syncResponse.fields)
|
||||||
database.itemValueDao().insertValues(syncResponse.itemValues)
|
database.itemDao().insertItems(syncResponse.items)
|
||||||
|
|
||||||
|
// 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
|
// Update last sync timestamp
|
||||||
setLastSyncTimestamp(syncResponse.serverTimestamp)
|
setLastSyncTimestamp(syncResponse.serverTimestamp)
|
||||||
|
|
||||||
if (isInitialSync) {
|
if (isInitialSync) {
|
||||||
Logger.i("Sync", "✅ Initial sync completed")
|
Logger.i("Sync", "[SYNC] Initial sync completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return@withContext Result.success(Unit)
|
return@withContext Result.success(Unit)
|
||||||
} else {
|
} 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()}"))
|
return@withContext Result.failure(Exception("Sync failed: ${response.code()}"))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e("Sync", "❌ Sync error: ${e.message}")
|
Logger.e("Sync", "[ERROR] Sync error: ${e.message}")
|
||||||
return@withContext Result.failure(e)
|
return@withContext Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user