feat(backup): add backup record reads to FirestoreConversationDataSource (getConversationsForBackup, getBackupRecords)
This commit is contained in:
parent
522823f739
commit
909d261b6c
|
|
@ -2,6 +2,7 @@ package app.closer.data.remote
|
||||||
|
|
||||||
import app.closer.crypto.CoupleEncryptionManager
|
import app.closer.crypto.CoupleEncryptionManager
|
||||||
import app.closer.crypto.FieldEncryptor
|
import app.closer.crypto.FieldEncryptor
|
||||||
|
import app.closer.domain.model.BackupMessageRecord
|
||||||
import app.closer.domain.model.Conversation
|
import app.closer.domain.model.Conversation
|
||||||
import app.closer.domain.model.QuestionMessage
|
import app.closer.domain.model.QuestionMessage
|
||||||
import com.google.firebase.firestore.DocumentSnapshot
|
import com.google.firebase.firestore.DocumentSnapshot
|
||||||
|
|
@ -232,6 +233,58 @@ class FirestoreConversationDataSource @Inject constructor(
|
||||||
return runCatching { aead.decrypt(cipher, coupleId.toByteArray(Charsets.UTF_8)) }.getOrNull()
|
return runCatching { aead.decrypt(cipher, coupleId.toByteArray(Charsets.UTF_8)) }.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Backup reads (source of truth for the encrypted backup) ─────────────────────
|
||||||
|
|
||||||
|
/** All conversations for the couple as (id, type) — `main` + per-question threads. */
|
||||||
|
suspend fun getConversationsForBackup(coupleId: String): List<Pair<String, String>> =
|
||||||
|
runCatching {
|
||||||
|
conversationsRef(coupleId).get().await().documents.map {
|
||||||
|
it.id to (it.getString("type") ?: "main")
|
||||||
|
}
|
||||||
|
}.getOrDefault(emptyList())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup records for one conversation with a resolved `createdAt` strictly after [afterCreatedAt]
|
||||||
|
* (0 = from the beginning). Pending-write docs (null server timestamp) are skipped so the cursor
|
||||||
|
* never straddles an unresolved message. Includes tombstones + reactions for fidelity.
|
||||||
|
*/
|
||||||
|
suspend fun getBackupRecords(
|
||||||
|
coupleId: String,
|
||||||
|
conversationId: String,
|
||||||
|
conversationType: String,
|
||||||
|
afterCreatedAt: Long
|
||||||
|
): List<BackupMessageRecord> = runCatching {
|
||||||
|
var query: Query = messagesRef(coupleId, conversationId).orderBy("createdAt", Query.Direction.ASCENDING)
|
||||||
|
if (afterCreatedAt > 0L) {
|
||||||
|
query = query.whereGreaterThan("createdAt", com.google.firebase.Timestamp(java.util.Date(afterCreatedAt)))
|
||||||
|
}
|
||||||
|
query.get().await().documents.mapNotNull { it.toBackupRecord(conversationId, conversationType) }
|
||||||
|
}.getOrDefault(emptyList())
|
||||||
|
|
||||||
|
private fun DocumentSnapshot.toBackupRecord(
|
||||||
|
conversationId: String,
|
||||||
|
conversationType: String
|
||||||
|
): BackupMessageRecord? {
|
||||||
|
// Skip unresolved server timestamps — back them up once they settle.
|
||||||
|
val createdAt = getTimestamp("createdAt")?.toDate()?.time ?: return null
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val reactions = (get("reactions") as? Map<String, String>).orEmpty()
|
||||||
|
val type = getString("type") ?: "text"
|
||||||
|
return BackupMessageRecord(
|
||||||
|
messageId = id,
|
||||||
|
conversationId = conversationId,
|
||||||
|
conversationType = conversationType,
|
||||||
|
authorUserId = getString("authorUserId") ?: "",
|
||||||
|
type = type,
|
||||||
|
encText = if (type == "text") getString("text") else null,
|
||||||
|
mediaUrl = getString("mediaUrl"),
|
||||||
|
durationMs = getLong("durationMs"),
|
||||||
|
createdAt = createdAt,
|
||||||
|
deleted = getBoolean("deleted") ?: false,
|
||||||
|
reactions = reactions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Mappers / await helpers ────────────────────────────────────────────────────
|
// ─── Mappers / await helpers ────────────────────────────────────────────────────
|
||||||
|
|
||||||
private fun DocumentSnapshot.toConversation(
|
private fun DocumentSnapshot.toConversation(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue