refactor: centralize all Firestore collection paths in FirestoreCollections constants

This commit is contained in:
null 2026-06-17 19:47:58 -05:00
parent d86a5de2a0
commit 662a49df9d
11 changed files with 77 additions and 27 deletions

View File

@ -1,5 +1,6 @@
package app.closer.core.billing
import app.closer.data.remote.FirestoreCollections
import app.closer.domain.model.AuthState
import app.closer.domain.repository.AuthRepository
import com.google.firebase.firestore.DocumentSnapshot
@ -125,7 +126,9 @@ class FirestoreEntitlementChecker @Inject constructor(
}
private fun entitlementsRef(userId: String) =
firestore.collection("users").document(userId).collection("entitlements").document("premium")
firestore.collection(FirestoreCollections.USERS).document(userId)
.collection(FirestoreCollections.Users.ENTITLEMENTS)
.document(FirestoreCollections.Users.ENTITLEMENT_PREMIUM_DOC)
companion object {
private const val PREMIUM_ENTITLEMENT_ID = "closer_premium"

View File

@ -26,7 +26,8 @@ class FirestoreBucketListDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun itemsRef(coupleId: String) =
db.collection("couples").document(coupleId).collection("bucket_list")
db.collection(FirestoreCollections.COUPLES).document(coupleId)
.collection(FirestoreCollections.Couples.BUCKET_LIST)
// ─── CRUD methods ────────────────────────────────────────────────────────

View File

@ -0,0 +1,40 @@
package app.closer.data.remote
/**
* Single source of truth for all Firestore collection and fixed document-ID names.
*
* Use these constants everywhere instead of inline string literals so that a path
* rename requires a change in exactly one place and the compiler catches every caller.
*/
object FirestoreCollections {
// ── Top-level collections ─────────────────────────────────────────────────
const val USERS = "users"
const val COUPLES = "couples"
const val INVITES = "invites"
// ── Subcollections under users/{uid} ──────────────────────────────────────
object Users {
const val ENTITLEMENTS = "entitlements"
const val FCM_TOKENS = "fcmTokens"
const val ENTITLEMENT_PREMIUM_DOC = "premium"
}
// ── Subcollections under couples/{coupleId} ───────────────────────────────
object Couples {
const val SESSIONS = "sessions"
const val QUESTION_THREADS = "question_threads"
const val DATE_SWIPES = "date_swipes"
const val DATE_MATCHES = "date_matches"
const val DATE_PLAN_PREFERENCES = "date_plan_preferences"
const val DATE_PLANS = "date_plans"
const val BUCKET_LIST = "bucket_list"
}
// ── Subcollections under …/question_threads/{threadId} ────────────────────
object QuestionThreads {
const val ANSWERS = "answers"
const val MESSAGES = "messages"
const val REACTIONS = "reactions"
}
}

View File

@ -15,8 +15,8 @@ import kotlin.coroutines.resumeWithException
class FirestoreCoupleDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun coupleRef(coupleId: String) = db.collection("couples").document(coupleId)
private fun userRef(uid: String) = db.collection("users").document(uid)
private fun coupleRef(coupleId: String) = db.collection(FirestoreCollections.COUPLES).document(coupleId)
private fun userRef(uid: String) = db.collection(FirestoreCollections.USERS).document(uid)
suspend fun createCouple(inviterUserId: String, acceptorUserId: String, inviteCode: String): String {
val coupleId = UUID.randomUUID().toString()
@ -89,7 +89,7 @@ class FirestoreCoupleDataSource @Inject constructor() {
suspend fun leaveCouple(userId: String) {
val userSnap = suspendCancellableCoroutine<DocumentSnapshot> { cont ->
db.collection("users").document(userId).get()
userRef(userId).get()
.addOnSuccessListener { cont.resume(it) }
.addOnFailureListener { cont.resumeWithException(it) }
}
@ -104,7 +104,7 @@ class FirestoreCoupleDataSource @Inject constructor() {
suspendCancellableCoroutine<Unit> { cont ->
val batch = db.batch()
allUserIds.forEach { uid ->
batch.update(db.collection("users").document(uid), "coupleId", null)
batch.update(userRef(uid), "coupleId", null)
}
batch.commit()
.addOnSuccessListener { cont.resume(Unit) }

View File

@ -36,7 +36,8 @@ class FirestoreDateMatchDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun matchesRef(coupleId: String) =
db.collection("couples").document(coupleId).collection("date_matches")
db.collection(FirestoreCollections.COUPLES).document(coupleId)
.collection(FirestoreCollections.Couples.DATE_MATCHES)
suspend fun createMatch(coupleId: String, dateIdeaId: String, matchedBy: List<String>): String {
val doc = matchesRef(coupleId).document()

View File

@ -29,10 +29,12 @@ class FirestoreDatePlanDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun preferencesRef(coupleId: String) =
db.collection("couples").document(coupleId).collection("date_plan_preferences")
db.collection(FirestoreCollections.COUPLES).document(coupleId)
.collection(FirestoreCollections.Couples.DATE_PLAN_PREFERENCES)
private fun plansRef(coupleId: String) =
db.collection("couples").document(coupleId).collection("date_plans")
db.collection(FirestoreCollections.COUPLES).document(coupleId)
.collection(FirestoreCollections.Couples.DATE_PLANS)
// ─── Preference methods ──────────────────────────────────────────────────

View File

@ -37,7 +37,8 @@ class FirestoreDateSwipeDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun swipesRef(coupleId: String) =
db.collection("couples").document(coupleId).collection("date_swipes")
db.collection(FirestoreCollections.COUPLES).document(coupleId)
.collection(FirestoreCollections.Couples.DATE_SWIPES)
suspend fun recordSwipe(coupleId: String, swipe: DateSwipe) {
val path = swipesRef(coupleId).document(swipe.dateIdeaId)

View File

@ -14,7 +14,7 @@ import kotlin.random.Random
class FirestoreInviteDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun inviteRef(code: String) = db.collection("invites").document(code)
private fun inviteRef(code: String) = db.collection(FirestoreCollections.INVITES).document(code)
fun generateCode(): String = (1..6)
.map { CODE_CHARS[Random.nextInt(CODE_CHARS.length)] }

View File

@ -24,7 +24,8 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun threadsRef(coupleId: String) =
db.collection("couples").document(coupleId).collection("question_threads")
db.collection(FirestoreCollections.COUPLES).document(coupleId)
.collection(FirestoreCollections.Couples.QUESTION_THREADS)
// ─── Thread ─────────────────────────────────────────────────────────────────
@ -77,7 +78,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
val now = FieldValue.serverTimestamp()
threadsRef(coupleId)
.document(threadId)
.collection("answers")
.collection(FirestoreCollections.QuestionThreads.ANSWERS)
.document(userId)
.set(
mapOf(
@ -96,7 +97,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
suspend fun getAnswerCount(coupleId: String, threadId: String): Int {
val snap = threadsRef(coupleId)
.document(threadId)
.collection("answers")
.collection(FirestoreCollections.QuestionThreads.ANSWERS)
.getAwait()
return snap.size()
}
@ -104,7 +105,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
fun observeAnswers(coupleId: String, threadId: String): Flow<List<QuestionAnswer>> = callbackFlow {
val listener = threadsRef(coupleId)
.document(threadId)
.collection("answers")
.collection(FirestoreCollections.QuestionThreads.ANSWERS)
.addSnapshotListener { snap, err ->
if (err != null || snap == null) return@addSnapshotListener
trySend(snap.documents.mapNotNull { it.toQuestionAnswer() })
@ -117,7 +118,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
suspend fun sendMessage(coupleId: String, threadId: String, message: QuestionMessage) {
threadsRef(coupleId)
.document(threadId)
.collection("messages")
.collection(FirestoreCollections.QuestionThreads.MESSAGES)
.add(
mapOf(
"userId" to message.userId,
@ -130,7 +131,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
fun observeMessages(coupleId: String, threadId: String): Flow<List<QuestionMessage>> = callbackFlow {
val listener = threadsRef(coupleId)
.document(threadId)
.collection("messages")
.collection(FirestoreCollections.QuestionThreads.MESSAGES)
.orderBy("createdAt", Query.Direction.ASCENDING)
.addSnapshotListener { snap, err ->
if (err != null || snap == null) return@addSnapshotListener
@ -145,7 +146,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
val docId = "${reaction.userId}_${reaction.targetUserId}"
threadsRef(coupleId)
.document(threadId)
.collection("reactions")
.collection(FirestoreCollections.QuestionThreads.REACTIONS)
.document(docId)
.set(
mapOf(
@ -160,7 +161,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor() {
fun observeReactions(coupleId: String, threadId: String): Flow<List<QuestionReaction>> = callbackFlow {
val listener = threadsRef(coupleId)
.document(threadId)
.collection("reactions")
.collection(FirestoreCollections.QuestionThreads.REACTIONS)
.addSnapshotListener { snap, err ->
if (err != null || snap == null) return@addSnapshotListener
trySend(snap.documents.mapNotNull { it.toQuestionReaction() })

View File

@ -14,7 +14,7 @@ import kotlin.coroutines.resumeWithException
class FirestoreUserDataSource @Inject constructor() {
private val db = FirebaseFirestore.getInstance()
private fun userRef(uid: String) = db.collection("users").document(uid)
private fun userRef(uid: String) = db.collection(FirestoreCollections.USERS).document(uid)
suspend fun getUser(uid: String): User? =
suspendCancellableCoroutine { cont ->
@ -89,7 +89,7 @@ class FirestoreUserDataSource @Inject constructor() {
token: String,
metadata: DeviceMetadata
): Unit = suspendCancellableCoroutine { cont ->
userRef(uid).collection("fcmTokens").document(token)
userRef(uid).collection(FirestoreCollections.Users.FCM_TOKENS).document(token)
.set(
mapOf(
"token" to token,

View File

@ -1,5 +1,6 @@
package app.closer.data.repository
import app.closer.data.remote.FirestoreCollections
import app.closer.domain.model.QuestionSession
import app.closer.domain.repository.QuestionSessionRepository
import com.google.firebase.firestore.FirebaseFirestore
@ -14,14 +15,14 @@ class QuestionSessionRepositoryImpl @Inject constructor(
override suspend fun saveSession(session: QuestionSession): Result<Unit> = runCatching {
val doc = if (session.id.isBlank()) {
firestore.collection("couples")
firestore.collection(FirestoreCollections.COUPLES)
.document(session.coupleId)
.collection("sessions")
.collection(FirestoreCollections.Couples.SESSIONS)
.document()
} else {
firestore.collection("couples")
firestore.collection(FirestoreCollections.COUPLES)
.document(session.coupleId)
.collection("sessions")
.collection(FirestoreCollections.Couples.SESSIONS)
.document(session.id)
}
@ -41,9 +42,9 @@ class QuestionSessionRepositoryImpl @Inject constructor(
override suspend fun getSessionsForCouple(coupleId: String): Result<List<QuestionSession>> =
runCatching {
firestore.collection("couples")
firestore.collection(FirestoreCollections.COUPLES)
.document(coupleId)
.collection("sessions")
.collection(FirestoreCollections.Couples.SESSIONS)
.orderBy("completedAt", com.google.firebase.firestore.Query.Direction.DESCENDING)
.limit(50)
.get()