fix: add input sanitization across date plan forms and repository
This commit is contained in:
parent
b049024ba9
commit
0095151bd9
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue