feat(date-memories): add FirestoreDateMemoryDataSource

This commit is contained in:
null 2026-06-30 18:13:56 -05:00
parent de597f6238
commit 631064fcfe
1 changed files with 74 additions and 0 deletions

View File

@ -0,0 +1,74 @@
package app.closer.data.remote
import app.closer.domain.model.DateMemory
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import com.google.firebase.firestore.SetOptions
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
/**
* Completed-date log for the Date Replay timeline: `couples/{coupleId}/date_history/{id}`.
* PLAINTEXT (date-idea title/category + timestamp coarse metadata, not private words; the private
* reflection lives E2EE in [FirestoreDateReflectionDataSource]). Doc id = source matchId idempotent.
*/
@Singleton
class FirestoreDateMemoryDataSource @Inject constructor(private val db: FirebaseFirestore) {
private fun historyRef(coupleId: String) =
db.collection(FirestoreCollections.COUPLES).document(coupleId)
.collection(FirestoreCollections.Couples.DATE_HISTORY)
private fun toMemory(d: com.google.firebase.firestore.DocumentSnapshot) = DateMemory(
id = d.id,
dateIdeaId = d.getString("dateIdeaId") ?: "",
title = d.getString("title") ?: "",
category = d.getString("category") ?: "",
completedAt = d.getLong("completedAt") ?: 0L,
addedBy = d.getString("addedBy") ?: ""
)
/** Idempotent (merge on doc id = matchId — both partners marking the same date = one record). */
suspend fun markCompleted(coupleId: String, memory: DateMemory): Unit =
suspendCancellableCoroutine { cont ->
historyRef(coupleId).document(memory.id).set(
mapOf(
"dateIdeaId" to memory.dateIdeaId,
"title" to memory.title,
"category" to memory.category,
"completedAt" to memory.completedAt,
"addedBy" to memory.addedBy
),
SetOptions.merge()
).addOnSuccessListener { cont.resume(Unit) }.addOnFailureListener { cont.resumeWithException(it) }
}
fun observeHistory(coupleId: String): Flow<List<DateMemory>> = callbackFlow {
val reg = historyRef(coupleId)
.orderBy("completedAt", Query.Direction.DESCENDING)
.addSnapshotListener { snap, err ->
if (err != null) { close(err); return@addSnapshotListener }
trySend(snap?.documents?.map(::toMemory) ?: emptyList())
}
awaitClose { reg.remove() }
}
suspend fun getHistoryOnce(coupleId: String): List<DateMemory> =
suspendCancellableCoroutine { cont ->
historyRef(coupleId).orderBy("completedAt", Query.Direction.DESCENDING).get()
.addOnSuccessListener { snap -> cont.resume(snap.documents.map(::toMemory)) }
.addOnFailureListener { cont.resumeWithException(it) }
}
suspend fun delete(coupleId: String, id: String): Unit =
suspendCancellableCoroutine { cont ->
historyRef(coupleId).document(id).delete()
.addOnSuccessListener { cont.resume(Unit) }.addOnFailureListener { cont.resumeWithException(it) }
}
}