feat(date-memories): add FirestoreDateMemoryDataSource
This commit is contained in:
parent
de597f6238
commit
631064fcfe
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue