diff --git a/app/src/main/java/app/closer/data/repository/DatePlanRepositoryImpl.kt b/app/src/main/java/app/closer/data/repository/DatePlanRepositoryImpl.kt index 94a97c3f..8dcb9e78 100644 --- a/app/src/main/java/app/closer/data/repository/DatePlanRepositoryImpl.kt +++ b/app/src/main/java/app/closer/data/repository/DatePlanRepositoryImpl.kt @@ -77,18 +77,16 @@ class DatePlanRepositoryImpl @Inject constructor( } override suspend fun savePlan(plan: DatePlan) { - // Store in Room for local-first access - val entity = plan.toEntity() - - if (plan.id.isEmpty()) { - // Create new plan - let Firestore generate the ID - val newId = planDataSource.createPlan(plan.coupleId, plan) + val sanitized = plan.sanitized() + val entity = sanitized.toEntity() + + if (sanitized.id.isEmpty()) { + val newId = planDataSource.createPlan(sanitized.coupleId, sanitized) val updatedEntity = entity.copy(id = newId) planDao.insert(updatedEntity) } else { - // Update existing plan 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 ───────────────────────────────────────────────────────────── + 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( id = id, coupleId = coupleId, diff --git a/app/src/main/java/app/closer/ui/dates/BucketListViewModel.kt b/app/src/main/java/app/closer/ui/dates/BucketListViewModel.kt index f66bc085..71906749 100644 --- a/app/src/main/java/app/closer/ui/dates/BucketListViewModel.kt +++ b/app/src/main/java/app/closer/ui/dates/BucketListViewModel.kt @@ -45,8 +45,8 @@ class BucketListViewModel @Inject constructor( val newItem = BucketListItem( coupleId = coupleId, - title = title, - description = description, + title = title.take(MAX_TITLE_LENGTH), + description = description.take(MAX_DESCRIPTION_LENGTH), category = category, addedBy = "currentUser", addedAt = System.currentTimeMillis() @@ -100,6 +100,11 @@ class BucketListViewModel @Inject constructor( fun clear() { _uiState.update { BucketListUiState() } } + + private companion object { + const val MAX_TITLE_LENGTH = 100 + const val MAX_DESCRIPTION_LENGTH = 500 + } } data class BucketListUiState( diff --git a/app/src/main/java/app/closer/ui/dates/DateBuilderViewModel.kt b/app/src/main/java/app/closer/ui/dates/DateBuilderViewModel.kt index 1b7aed90..19d5cce5 100644 --- a/app/src/main/java/app/closer/ui/dates/DateBuilderViewModel.kt +++ b/app/src/main/java/app/closer/ui/dates/DateBuilderViewModel.kt @@ -47,9 +47,9 @@ class DateBuilderViewModel @Inject constructor( val preference = DatePlanPreference( dateIdeaId = state.dateIdeaId, preferredDate = state.scheduledDate, - preferredTime = state.scheduledTime, + preferredTime = state.scheduledTime.take(MAX_TIME_LENGTH), budget = state.budget, - duration = state.duration + duration = state.duration.take(MAX_DURATION_LENGTH) ) viewModelScope.launch { @@ -60,6 +60,11 @@ class DateBuilderViewModel @Inject constructor( fun clear() { _uiState.update { DateBuilderUiState() } } + + private companion object { + const val MAX_TIME_LENGTH = 20 + const val MAX_DURATION_LENGTH = 50 + } } data class DateBuilderUiState(