chore(gitignore): ignore .code-workspace; cleanup Firebase data source dead code
This commit is contained in:
parent
39fbd943ff
commit
f160f141d6
|
|
@ -90,3 +90,4 @@ Future.md
|
|||
docs/strategy/positioning-vs-paired.md
|
||||
ClaudeQAPlan.md
|
||||
ClaudeReport.md
|
||||
relationship-app.code-workspace
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ import android.net.Uri
|
|||
import com.google.firebase.storage.FirebaseStorage
|
||||
import com.google.firebase.storage.StorageMetadata
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
@Singleton
|
||||
class FirebaseStorageDataSource @Inject constructor(
|
||||
|
|
@ -31,19 +29,13 @@ class FirebaseStorageDataSource @Inject constructor(
|
|||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
|
||||
suspend fun uploadProfilePhoto(uid: String, uri: Uri): String =
|
||||
suspendCancellableCoroutine { cont ->
|
||||
suspend fun uploadProfilePhoto(uid: String, uri: Uri): String {
|
||||
val ref = storage.reference.child("users/$uid/profile.jpg")
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
?.takeIf { it.startsWith("image/") }
|
||||
?: "image/jpeg"
|
||||
val metadata = StorageMetadata.Builder()
|
||||
.setContentType(mimeType)
|
||||
.build()
|
||||
ref.putFile(uri, metadata)
|
||||
.continueWithTask { ref.downloadUrl }
|
||||
.addOnSuccessListener { cont.resume(it.toString()) }
|
||||
.addOnFailureListener { cont.resumeWithException(it) }
|
||||
val metadata = StorageMetadata.Builder().setContentType(mimeType).build()
|
||||
return ref.putFile(uri, metadata).continueWithTask { ref.downloadUrl }.await().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -51,21 +43,12 @@ class FirebaseStorageDataSource @Inject constructor(
|
|||
* profile-photo ownership model) and returns the tokenized download URL. The bytes are
|
||||
* ciphertext, so Storage never holds anything readable.
|
||||
*/
|
||||
suspend fun uploadEncryptedMedia(uid: String, encryptedBytes: ByteArray): String =
|
||||
suspendCancellableCoroutine { cont ->
|
||||
suspend fun uploadEncryptedMedia(uid: String, encryptedBytes: ByteArray): String {
|
||||
// Fail fast when clearly offline so the chat shows a retry instead of a stuck spinner.
|
||||
if (!isOnline()) {
|
||||
cont.resumeWithException(IOException("No internet connection"))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
if (!isOnline()) throw IOException("No internet connection")
|
||||
val ref = storage.reference.child("users/$uid/chat_media/${java.util.UUID.randomUUID()}")
|
||||
val metadata = StorageMetadata.Builder()
|
||||
.setContentType("application/octet-stream")
|
||||
.build()
|
||||
ref.putBytes(encryptedBytes, metadata)
|
||||
.continueWithTask { ref.downloadUrl }
|
||||
.addOnSuccessListener { cont.resume(it.toString()) }
|
||||
.addOnFailureListener { cont.resumeWithException(it) }
|
||||
val metadata = StorageMetadata.Builder().setContentType("application/octet-stream").build()
|
||||
return ref.putBytes(encryptedBytes, metadata).continueWithTask { ref.downloadUrl }.await().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,29 +57,18 @@ class FirebaseStorageDataSource @Inject constructor(
|
|||
* can fetch it). The bytes are couple-key ciphertext, so Storage holds nothing readable. Living
|
||||
* under users/{uid}/ means the existing account-delete cleanup covers backups too.
|
||||
*/
|
||||
suspend fun uploadBackupSnapshot(uid: String, snapshotId: String, encryptedBytes: ByteArray): String =
|
||||
suspendCancellableCoroutine { cont ->
|
||||
if (!isOnline()) {
|
||||
cont.resumeWithException(IOException("No internet connection"))
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
suspend fun uploadBackupSnapshot(uid: String, snapshotId: String, encryptedBytes: ByteArray): String {
|
||||
if (!isOnline()) throw IOException("No internet connection")
|
||||
val ref = storage.reference.child("users/$uid/backups/$snapshotId")
|
||||
val metadata = StorageMetadata.Builder()
|
||||
.setContentType("application/octet-stream")
|
||||
.build()
|
||||
ref.putBytes(encryptedBytes, metadata)
|
||||
.continueWithTask { ref.downloadUrl }
|
||||
.addOnSuccessListener { cont.resume(it.toString()) }
|
||||
.addOnFailureListener { cont.resumeWithException(it) }
|
||||
val metadata = StorageMetadata.Builder().setContentType("application/octet-stream").build()
|
||||
return ref.putBytes(encryptedBytes, metadata).continueWithTask { ref.downloadUrl }.await().toString()
|
||||
}
|
||||
|
||||
/** Best-effort delete of a previously-uploaded backup snapshot (post-compaction cleanup; succeeds
|
||||
* only for the uploader's own path, so a cross-partner stale blob is left for that owner/cleanup). */
|
||||
suspend fun deleteBackupSnapshot(uid: String, snapshotId: String): Unit =
|
||||
suspendCancellableCoroutine { cont ->
|
||||
storage.reference.child("users/$uid/backups/$snapshotId").delete()
|
||||
.addOnSuccessListener { cont.resume(Unit) }
|
||||
.addOnFailureListener { cont.resume(Unit) } // best-effort; a leftover blob is harmless ciphertext
|
||||
suspend fun deleteBackupSnapshot(uid: String, snapshotId: String) {
|
||||
// Best-effort: a leftover blob is harmless ciphertext, so swallow failures.
|
||||
runCatching { storage.reference.child("users/$uid/backups/$snapshotId").delete().await() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,11 +11,9 @@ 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 kotlinx.coroutines.tasks.await
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
/**
|
||||
* Firestore data source for per-couple, per-date swipe state.
|
||||
|
|
@ -57,7 +55,7 @@ class FirestoreDateSwipeDataSource @Inject constructor(
|
|||
path.set(
|
||||
mapOf("actions" to mapOf(swipe.userId to entry)),
|
||||
SetOptions.merge()
|
||||
).voidAwait()
|
||||
).await()
|
||||
}
|
||||
|
||||
/** Decrypt a stored action, tolerating legacy plaintext written before E2EE. */
|
||||
|
|
@ -72,7 +70,7 @@ class FirestoreDateSwipeDataSource @Inject constructor(
|
|||
|
||||
suspend fun getSwipe(coupleId: String, dateIdeaId: String, userId: String): DateSwipe? {
|
||||
val aead = encryptionManager.aeadFor(coupleId)
|
||||
val snap = swipesRef(coupleId).document(dateIdeaId).getDoc()
|
||||
val snap = swipesRef(coupleId).document(dateIdeaId).get().await()
|
||||
return snap.toDateSwipe(dateIdeaId, userId, aead, coupleId)
|
||||
}
|
||||
|
||||
|
|
@ -103,20 +101,13 @@ class FirestoreDateSwipeDataSource @Inject constructor(
|
|||
@Suppress("UNCHECKED_CAST")
|
||||
private suspend fun getAllSwipesForUser(coupleId: String, userId: String): List<DateSwipe> {
|
||||
val aead = encryptionManager.aeadFor(coupleId)
|
||||
val snap = swipesRef(coupleId).getQuery()
|
||||
val snap = swipesRef(coupleId).get().await()
|
||||
return snap.documents.mapNotNull { it.toDateSwipe(it.id, userId, aead, coupleId) }
|
||||
}
|
||||
|
||||
private suspend fun com.google.firebase.firestore.CollectionReference.getQuery() =
|
||||
suspendCancellableCoroutine { cont ->
|
||||
get()
|
||||
.addOnSuccessListener { cont.resume(it) }
|
||||
.addOnFailureListener { cont.resumeWithException(it) }
|
||||
}
|
||||
|
||||
suspend fun getAllSwipesForDate(coupleId: String, dateIdeaId: String): List<DateSwipe> {
|
||||
val aead = encryptionManager.aeadFor(coupleId)
|
||||
val snap = swipesRef(coupleId).document(dateIdeaId).getDoc()
|
||||
val snap = swipesRef(coupleId).document(dateIdeaId).get().await()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val actions = snap.get("actions") as? Map<String, Map<String, Any>> ?: emptyMap()
|
||||
return actions.map { (uid, data) ->
|
||||
|
|
@ -129,21 +120,6 @@ class FirestoreDateSwipeDataSource @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// ─── Coroutine helpers ───────────────────────────────────────────────────
|
||||
|
||||
private suspend fun com.google.firebase.firestore.DocumentReference.getDoc(): DocumentSnapshot =
|
||||
suspendCancellableCoroutine { cont ->
|
||||
get()
|
||||
.addOnSuccessListener { cont.resume(it) }
|
||||
.addOnFailureListener { cont.resumeWithException(it) }
|
||||
}
|
||||
|
||||
private suspend fun com.google.android.gms.tasks.Task<Void>.voidAwait() =
|
||||
suspendCancellableCoroutine<Unit> { cont ->
|
||||
addOnSuccessListener { cont.resume(Unit) }
|
||||
addOnFailureListener { cont.resumeWithException(it) }
|
||||
}
|
||||
|
||||
// ─── Mappers ─────────────────────────────────────────────────────────────
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
|
|
|||
Loading…
Reference in New Issue