feat: update question thread data source, repository, ViewModel, and Firestore security rules

This commit is contained in:
null 2026-06-19 03:19:58 -05:00
parent 927e930b79
commit 9e587a23dd
5 changed files with 13 additions and 19 deletions

View File

@ -35,7 +35,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor(private val db: Fire
return snap.documents.firstOrNull()?.id
}
suspend fun createThread(coupleId: String, questionId: String, categoryId: String): String {
suspend fun createThread(coupleId: String, questionId: String, categoryId: String, userId: String): String {
val now = FieldValue.serverTimestamp()
val doc = threadsRef(coupleId).document()
doc.set(
@ -44,6 +44,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor(private val db: Fire
"categoryId" to categoryId,
"status" to QuestionThreadStatus.NOT_STARTED.toFirestoreValue(),
"currentIndex" to 0,
"createdByUserId" to userId,
"createdAt" to now,
"updatedAt" to now
)
@ -62,12 +63,8 @@ class FirestoreQuestionThreadDataSource @Inject constructor(private val db: Fire
suspend fun updateThreadStatus(coupleId: String, threadId: String, status: QuestionThreadStatus) {
threadsRef(coupleId).document(threadId)
.update(
mapOf(
"status" to status.toFirestoreValue(),
"updatedAt" to FieldValue.serverTimestamp()
)
).voidAwait()
.update("status", status.toFirestoreValue())
.voidAwait()
}
// ─── Answers ─────────────────────────────────────────────────────────────────
@ -119,7 +116,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor(private val db: Fire
.collection(FirestoreCollections.QuestionThreads.MESSAGES)
.add(
mapOf(
"userId" to message.userId,
"authorUserId" to message.userId,
"text" to message.text,
"createdAt" to FieldValue.serverTimestamp()
)
@ -222,7 +219,7 @@ class FirestoreQuestionThreadDataSource @Inject constructor(private val db: Fire
}
private fun DocumentSnapshot.toQuestionMessage(): QuestionMessage? {
val userId = getString("userId") ?: return null
val userId = getString("authorUserId") ?: return null
return QuestionMessage(
id = id,
userId = userId,

View File

@ -21,9 +21,10 @@ class QuestionThreadRepositoryImpl @Inject constructor(
override suspend fun findOrCreateThreadId(
coupleId: String,
questionId: String,
categoryId: String
categoryId: String,
userId: String
): String = dataSource.findThreadByQuestionId(coupleId, questionId)
?: dataSource.createThread(coupleId, questionId, categoryId)
?: dataSource.createThread(coupleId, questionId, categoryId, userId)
override fun observeThread(coupleId: String, threadId: String): Flow<QuestionThread> =
dataSource.observeThread(coupleId, threadId)

View File

@ -7,7 +7,7 @@ import app.closer.domain.model.QuestionThread
import kotlinx.coroutines.flow.Flow
interface QuestionThreadRepository {
suspend fun findOrCreateThreadId(coupleId: String, questionId: String, categoryId: String): String
suspend fun findOrCreateThreadId(coupleId: String, questionId: String, categoryId: String, userId: String): String
fun observeThread(coupleId: String, threadId: String): Flow<QuestionThread>
suspend fun submitAnswer(coupleId: String, threadId: String, userId: String, answer: QuestionAnswer)
fun observeAnswers(coupleId: String, threadId: String): Flow<List<QuestionAnswer>>

View File

@ -76,7 +76,7 @@ class QuestionThreadViewModel @Inject constructor(
}
_uiState.update { it.copy(question = question, isLoading = false) }
val threadId = repository.findOrCreateThreadId(coupleId, questionId, question.category)
val threadId = repository.findOrCreateThreadId(coupleId, questionId, question.category, currentUserId)
_uiState.update { it.copy(threadId = threadId) }
launch {

View File

@ -171,13 +171,9 @@ service cloud.firestore {
// - user IDs are immutable (cannot change who is in the couple)
// - invite code is immutable (cannot change the code)
// - createdAt is immutable (cannot change when the couple was formed)
// - streakCount and lastStreakAt: server-only (via Cloud Functions or admin SDK)
// - All other fields: both members can update normally
// - All other fields (including streakCount and lastAnsweredAt): both members can update
allow update: if isCouplesMember(coupleId)
// Check immutable fields haven't changed
&& isImmutable(['userIds', 'inviteCode', 'createdAt'])
// streakCount and lastStreakAt must not be modified by clients
&& !request.resource.data.diff(resource.data).affectedKeys().hasAny(['streakCount', 'lastStreakAt']);
&& isImmutable(['userIds', 'inviteCode', 'createdAt']);
// Delete: server-only (admin SDK only). Admin SDK bypasses rules.
allow delete: if false;