Add backend feedback submission and image preview
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -262,6 +262,83 @@ pub fn build_runtime_profile_recharge_order_create_input(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_feedback_submission_input(
|
||||
user_id: String,
|
||||
description: String,
|
||||
contact_phone: Option<String>,
|
||||
evidence_items: Vec<RuntimeProfileFeedbackEvidenceSnapshot>,
|
||||
created_at_micros: i64,
|
||||
) -> Result<RuntimeProfileFeedbackSubmissionInput, RuntimeProfileFieldError> {
|
||||
let user_id = normalize_runtime_profile_user_id(user_id)?;
|
||||
let description = normalize_required_string(description)
|
||||
.ok_or(RuntimeProfileFieldError::MissingFeedbackDescription)?;
|
||||
let description_chars = description.chars().count();
|
||||
if description_chars < PROFILE_FEEDBACK_DESCRIPTION_MIN_CHARS {
|
||||
return Err(RuntimeProfileFieldError::FeedbackDescriptionTooShort);
|
||||
}
|
||||
if description_chars > PROFILE_FEEDBACK_DESCRIPTION_MAX_CHARS {
|
||||
return Err(RuntimeProfileFieldError::FeedbackDescriptionTooLong);
|
||||
}
|
||||
|
||||
let contact_phone = normalize_optional_string(contact_phone);
|
||||
if contact_phone
|
||||
.as_deref()
|
||||
.map(|value| value.chars().count() > PROFILE_FEEDBACK_CONTACT_PHONE_MAX_CHARS)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(RuntimeProfileFieldError::FeedbackContactPhoneTooLong);
|
||||
}
|
||||
if evidence_items.len() > PROFILE_FEEDBACK_EVIDENCE_MAX_COUNT {
|
||||
return Err(RuntimeProfileFieldError::TooManyFeedbackEvidenceItems);
|
||||
}
|
||||
|
||||
let feedback_id = build_runtime_profile_feedback_submission_id(&user_id, created_at_micros);
|
||||
let mut total_size_bytes = 0u64;
|
||||
let mut normalized_evidence_items = Vec::with_capacity(evidence_items.len());
|
||||
for (index, item) in evidence_items.into_iter().enumerate() {
|
||||
let content_type = normalize_required_string(item.content_type)
|
||||
.map(|value| value.to_ascii_lowercase())
|
||||
.ok_or(RuntimeProfileFieldError::InvalidFeedbackEvidenceContentType)?;
|
||||
if !is_profile_feedback_image_content_type(&content_type) {
|
||||
return Err(RuntimeProfileFieldError::InvalidFeedbackEvidenceContentType);
|
||||
}
|
||||
if item.size_bytes == 0 || item.size_bytes > PROFILE_FEEDBACK_EVIDENCE_MAX_BYTES {
|
||||
return Err(RuntimeProfileFieldError::FeedbackEvidenceTooLarge);
|
||||
}
|
||||
|
||||
total_size_bytes = total_size_bytes
|
||||
.checked_add(item.size_bytes)
|
||||
.ok_or(RuntimeProfileFieldError::FeedbackEvidenceTotalTooLarge)?;
|
||||
if total_size_bytes > PROFILE_FEEDBACK_EVIDENCE_TOTAL_MAX_BYTES {
|
||||
return Err(RuntimeProfileFieldError::FeedbackEvidenceTotalTooLarge);
|
||||
}
|
||||
|
||||
let data_url = normalize_required_string(item.data_url)
|
||||
.ok_or(RuntimeProfileFieldError::InvalidFeedbackEvidenceDataUrl)?;
|
||||
if !has_profile_feedback_data_url_prefix(&data_url, &content_type) {
|
||||
return Err(RuntimeProfileFieldError::InvalidFeedbackEvidenceDataUrl);
|
||||
}
|
||||
let file_name = normalize_optional_string(Some(item.file_name))
|
||||
.unwrap_or_else(|| format!("feedback-image-{}", index + 1));
|
||||
|
||||
normalized_evidence_items.push(RuntimeProfileFeedbackEvidenceSnapshot {
|
||||
evidence_id: build_runtime_profile_feedback_evidence_id(&feedback_id, index),
|
||||
file_name,
|
||||
content_type,
|
||||
size_bytes: item.size_bytes,
|
||||
data_url,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(RuntimeProfileFeedbackSubmissionInput {
|
||||
user_id,
|
||||
description,
|
||||
contact_phone,
|
||||
evidence_items: normalized_evidence_items,
|
||||
created_at_micros,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_referral_invite_center_get_input(
|
||||
user_id: String,
|
||||
) -> Result<RuntimeReferralInviteCenterGetInput, RuntimeProfileFieldError> {
|
||||
@@ -595,6 +672,17 @@ pub fn build_runtime_browse_history_id(
|
||||
format!("{user_id}:{owner_user_id}:{profile_id}")
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_feedback_submission_id(
|
||||
user_id: &str,
|
||||
created_at_micros: i64,
|
||||
) -> String {
|
||||
format!("feedback:{}:{}", user_id.trim(), created_at_micros)
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_feedback_evidence_id(feedback_id: &str, index: usize) -> String {
|
||||
format!("{}:evidence:{:02}", feedback_id.trim(), index + 1)
|
||||
}
|
||||
|
||||
fn parse_utc_rfc3339_to_micros(value: &str) -> Option<i64> {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
@@ -630,6 +718,19 @@ fn normalize_current_story_json(
|
||||
.map_err(|_| RuntimeProfileFieldError::InvalidCurrentStoryJson)
|
||||
}
|
||||
|
||||
fn is_profile_feedback_image_content_type(value: &str) -> bool {
|
||||
matches!(
|
||||
value,
|
||||
"image/png" | "image/jpeg" | "image/jpg" | "image/webp" | "image/gif"
|
||||
)
|
||||
}
|
||||
|
||||
fn has_profile_feedback_data_url_prefix(data_url: &str, content_type: &str) -> bool {
|
||||
data_url
|
||||
.to_ascii_lowercase()
|
||||
.starts_with(&format!("data:{content_type};base64,"))
|
||||
}
|
||||
|
||||
pub fn normalize_invite_code(value: String) -> Option<String> {
|
||||
let normalized = value
|
||||
.trim()
|
||||
|
||||
Reference in New Issue
Block a user