feat: update question thread data source, repository, ViewModel, and Firestore security rules
This commit is contained in:
parent
927e930b79
commit
9e587a23dd
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue