fix: add input sanitization across date plan forms and repository

This commit is contained in:
null 2026-06-17 19:23:53 -05:00
parent b049024ba9
commit 0095151bd9
3 changed files with 41 additions and 12 deletions

View File

@ -77,18 +77,16 @@ class DatePlanRepositoryImpl @Inject constructor(
} }
override suspend fun savePlan(plan: DatePlan) { override suspend fun savePlan(plan: DatePlan) {
// Store in Room for local-first access val sanitized = plan.sanitized()
val entity = plan.toEntity() val entity = sanitized.toEntity()
if (plan.id.isEmpty()) { if (sanitized.id.isEmpty()) {
// Create new plan - let Firestore generate the ID val newId = planDataSource.createPlan(sanitized.coupleId, sanitized)
val newId = planDataSource.createPlan(plan.coupleId, plan)
val updatedEntity = entity.copy(id = newId) val updatedEntity = entity.copy(id = newId)
planDao.insert(updatedEntity) planDao.insert(updatedEntity)
} else { } else {
// Update existing plan
planDao.insert(entity) planDao.insert(entity)
planDataSource.updatePlan(plan.coupleId, plan) planDataSource.updatePlan(sanitized.coupleId, sanitized)
} }
} }
@ -135,8 +133,29 @@ class DatePlanRepositoryImpl @Inject constructor(
) )
} }
// ─── Sanitization ────────────────────────────────────────────────────────
private fun DatePlan.sanitized(): DatePlan = copy(
scheduledTime = scheduledTime.take(MAX_TIME_LENGTH),
duration = duration.take(MAX_DURATION_LENGTH),
activity = activity.take(MAX_TEXT_LENGTH),
food = food.take(MAX_TEXT_LENGTH),
conversationPrompts = conversationPrompts
.take(MAX_PROMPTS)
.map { it.take(MAX_PROMPT_LENGTH) },
optionalChallenge = optionalChallenge?.take(MAX_TEXT_LENGTH)
)
// ─── Mappers ───────────────────────────────────────────────────────────── // ─── Mappers ─────────────────────────────────────────────────────────────
private companion object {
const val MAX_TIME_LENGTH = 20
const val MAX_DURATION_LENGTH = 50
const val MAX_TEXT_LENGTH = 500
const val MAX_PROMPT_LENGTH = 1_000
const val MAX_PROMPTS = 20
}
private fun DatePlanPreferenceEntity.toDomain(): DatePlanPreference = DatePlanPreference( private fun DatePlanPreferenceEntity.toDomain(): DatePlanPreference = DatePlanPreference(
id = id, id = id,
coupleId = coupleId, coupleId = coupleId,

View File

@ -45,8 +45,8 @@ class BucketListViewModel @Inject constructor(
val newItem = BucketListItem( val newItem = BucketListItem(
coupleId = coupleId, coupleId = coupleId,
title = title, title = title.take(MAX_TITLE_LENGTH),
description = description, description = description.take(MAX_DESCRIPTION_LENGTH),
category = category, category = category,
addedBy = "currentUser", addedBy = "currentUser",
addedAt = System.currentTimeMillis() addedAt = System.currentTimeMillis()
@ -100,6 +100,11 @@ class BucketListViewModel @Inject constructor(
fun clear() { fun clear() {
_uiState.update { BucketListUiState() } _uiState.update { BucketListUiState() }
} }
private companion object {
const val MAX_TITLE_LENGTH = 100
const val MAX_DESCRIPTION_LENGTH = 500
}
} }
data class BucketListUiState( data class BucketListUiState(

View File

@ -47,9 +47,9 @@ class DateBuilderViewModel @Inject constructor(
val preference = DatePlanPreference( val preference = DatePlanPreference(
dateIdeaId = state.dateIdeaId, dateIdeaId = state.dateIdeaId,
preferredDate = state.scheduledDate, preferredDate = state.scheduledDate,
preferredTime = state.scheduledTime, preferredTime = state.scheduledTime.take(MAX_TIME_LENGTH),
budget = state.budget, budget = state.budget,
duration = state.duration duration = state.duration.take(MAX_DURATION_LENGTH)
) )
viewModelScope.launch { viewModelScope.launch {
@ -60,6 +60,11 @@ class DateBuilderViewModel @Inject constructor(
fun clear() { fun clear() {
_uiState.update { DateBuilderUiState() } _uiState.update { DateBuilderUiState() }
} }
private companion object {
const val MAX_TIME_LENGTH = 20
const val MAX_DURATION_LENGTH = 50
}
} }
data class DateBuilderUiState( data class DateBuilderUiState(