fix: sync profile approval unlock state
All checks were successful
build-and-release / build (developers) (push) Successful in 9s
build-and-release / build (customers) (push) Successful in 13s
build-and-release / build (companies) (push) Successful in 12s
build-and-release / build (cron) (push) Successful in 15s
build-and-release / build (catering-services) (push) Successful in 14s
build-and-release / build (fitness-trainers) (push) Successful in 6s
build-and-release / build (employees) (push) Successful in 15s
build-and-release / build (gateway) (push) Successful in 6s
build-and-release / build (graphic-designers) (push) Successful in 7s
build-and-release / build (job-seekers) (push) Successful in 6s
build-and-release / build (payments) (push) Successful in 7s
build-and-release / build (jobs) (push) Successful in 8s
build-and-release / build (makeup-artists) (push) Successful in 9s
build-and-release / build (photographers) (push) Successful in 6s
build-and-release / build (social-media-managers) (push) Successful in 7s
build-and-release / build (ugc-content-creators) (push) Successful in 6s
build-and-release / build (tutors) (push) Successful in 8s
build-and-release / build (video-editors) (push) Successful in 9s
build-and-release / build (users) (push) Successful in 6m14s
All checks were successful
build-and-release / build (developers) (push) Successful in 9s
build-and-release / build (customers) (push) Successful in 13s
build-and-release / build (companies) (push) Successful in 12s
build-and-release / build (cron) (push) Successful in 15s
build-and-release / build (catering-services) (push) Successful in 14s
build-and-release / build (fitness-trainers) (push) Successful in 6s
build-and-release / build (employees) (push) Successful in 15s
build-and-release / build (gateway) (push) Successful in 6s
build-and-release / build (graphic-designers) (push) Successful in 7s
build-and-release / build (job-seekers) (push) Successful in 6s
build-and-release / build (payments) (push) Successful in 7s
build-and-release / build (jobs) (push) Successful in 8s
build-and-release / build (makeup-artists) (push) Successful in 9s
build-and-release / build (photographers) (push) Successful in 6s
build-and-release / build (social-media-managers) (push) Successful in 7s
build-and-release / build (ugc-content-creators) (push) Successful in 6s
build-and-release / build (tutors) (push) Successful in 8s
build-and-release / build (video-editors) (push) Successful in 9s
build-and-release / build (users) (push) Successful in 6m14s
This commit is contained in:
parent
2da1ebc8be
commit
5f1b48d27b
2 changed files with 260 additions and 6 deletions
|
|
@ -19,6 +19,14 @@ use uuid::Uuid;
|
|||
|
||||
use super::role_meta::{role_key_to_display, role_to_table};
|
||||
|
||||
#[derive(Default)]
|
||||
struct RoleProfileSnapshot {
|
||||
display_name: Option<String>,
|
||||
email: Option<String>,
|
||||
phone: Option<String>,
|
||||
location: Option<String>,
|
||||
}
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_pending))
|
||||
|
|
@ -258,6 +266,8 @@ async fn activate_profile_after_final_approval(
|
|||
.await
|
||||
.ok();
|
||||
|
||||
sync_user_role_profile_after_final_decision(state, user_id, &role_key, "APPROVED", None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -285,11 +295,19 @@ async fn reject_profile_after_final_approval(
|
|||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let query = format!(
|
||||
"UPDATE {} SET status = 'REJECTED', updated_at = NOW() WHERE id = $1",
|
||||
table
|
||||
);
|
||||
sqlx::query(&query).bind(user_role_profile_id).execute(&state.pool).await?;
|
||||
if role_key == "COMPANY" || role_key == "JOB_SEEKER" || role_key == "CUSTOMER" {
|
||||
let query = format!(
|
||||
"UPDATE {} SET status = 'REJECTED', updated_at = NOW() WHERE user_id = $1",
|
||||
table
|
||||
);
|
||||
sqlx::query(&query).bind(user_id).execute(&state.pool).await?;
|
||||
} else {
|
||||
let query = format!(
|
||||
"UPDATE {} SET status = 'REJECTED', updated_at = NOW() WHERE user_role_profile_id = $1",
|
||||
table
|
||||
);
|
||||
sqlx::query(&query).bind(user_role_profile_id).execute(&state.pool).await?;
|
||||
}
|
||||
|
||||
if let Ok(user) = UserRepository::get_by_id(&state.pool, user_id).await {
|
||||
let display = role_key_to_display(&role_key);
|
||||
|
|
@ -319,6 +337,209 @@ async fn reject_profile_after_final_approval(
|
|||
.await
|
||||
.ok();
|
||||
|
||||
sync_user_role_profile_after_final_decision(
|
||||
state,
|
||||
user_id,
|
||||
&role_key,
|
||||
"REJECTED",
|
||||
reason,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_role_profile_snapshot(
|
||||
state: &AppState,
|
||||
user_id: Uuid,
|
||||
role_key: &str,
|
||||
) -> Result<RoleProfileSnapshot, sqlx::Error> {
|
||||
use sqlx::Row;
|
||||
|
||||
let user = UserRepository::get_by_id(&state.pool, user_id).await.ok();
|
||||
let fallback_name = user.as_ref().map(|u| {
|
||||
let full = format!(
|
||||
"{} {}",
|
||||
u.first_name.clone().unwrap_or_default(),
|
||||
u.last_name.clone().unwrap_or_default()
|
||||
)
|
||||
.trim()
|
||||
.to_string();
|
||||
if full.is_empty() { None } else { Some(full) }
|
||||
}).flatten();
|
||||
let fallback_email = user.as_ref().map(|u| u.email.clone());
|
||||
|
||||
if role_key == "COMPANY" {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT company_name, contact_email, contact_phone, city
|
||||
FROM company_profiles
|
||||
WHERE user_id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_optional(&state.pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = row {
|
||||
return Ok(RoleProfileSnapshot {
|
||||
display_name: row.try_get("company_name").ok().flatten().or(fallback_name),
|
||||
email: row.try_get("contact_email").ok().flatten().or(fallback_email),
|
||||
phone: row.try_get("contact_phone").ok().flatten(),
|
||||
location: row.try_get("city").ok().flatten(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut custom_data = serde_json::Value::Null;
|
||||
|
||||
if role_key == "JOB_SEEKER" || role_key == "CUSTOMER" {
|
||||
let table = if role_key == "JOB_SEEKER" {
|
||||
"job_seeker_profiles"
|
||||
} else {
|
||||
"customer_profiles"
|
||||
};
|
||||
let row = sqlx::query(&format!(
|
||||
r#"SELECT custom_data FROM {} WHERE user_id = $1"#,
|
||||
table
|
||||
))
|
||||
.bind(user_id)
|
||||
.fetch_optional(&state.pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = row {
|
||||
custom_data = row
|
||||
.try_get::<serde_json::Value, _>("custom_data")
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
.get("basic_info")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
} else if let Some(table) = role_to_table(role_key) {
|
||||
let urp_id = sqlx::query_scalar::<_, Uuid>(
|
||||
"SELECT id FROM user_role_profiles WHERE user_id = $1 AND role_key = $2",
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(role_key)
|
||||
.fetch_optional(&state.pool)
|
||||
.await?;
|
||||
|
||||
if let Some(urp_id) = urp_id {
|
||||
let row = sqlx::query(&format!(
|
||||
r#"SELECT custom_data FROM {} WHERE user_role_profile_id = $1"#,
|
||||
table
|
||||
))
|
||||
.bind(urp_id)
|
||||
.fetch_optional(&state.pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = row {
|
||||
custom_data = row
|
||||
.try_get::<serde_json::Value, _>("custom_data")
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let first_name = custom_data.get("first_name").and_then(|v| v.as_str()).map(str::trim).filter(|v| !v.is_empty());
|
||||
let last_name = custom_data.get("last_name").and_then(|v| v.as_str()).map(str::trim).filter(|v| !v.is_empty());
|
||||
let display_name = match (first_name, last_name) {
|
||||
(Some(first), Some(last)) => Some(format!("{} {}", first, last)),
|
||||
(Some(first), None) => Some(first.to_string()),
|
||||
(None, Some(last)) => Some(last.to_string()),
|
||||
(None, None) => custom_data
|
||||
.get("business_name")
|
||||
.or_else(|| custom_data.get("owner_name"))
|
||||
.or_else(|| custom_data.get("company_name"))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(String::from)
|
||||
.or(fallback_name),
|
||||
};
|
||||
|
||||
Ok(RoleProfileSnapshot {
|
||||
display_name,
|
||||
email: custom_data
|
||||
.get("email")
|
||||
.or_else(|| custom_data.get("company_email"))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(String::from)
|
||||
.or(fallback_email),
|
||||
phone: custom_data
|
||||
.get("phone")
|
||||
.or_else(|| custom_data.get("company_phone"))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(String::from),
|
||||
location: custom_data
|
||||
.get("location")
|
||||
.or_else(|| custom_data.get("city"))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(String::from),
|
||||
})
|
||||
}
|
||||
|
||||
async fn sync_user_role_profile_after_final_decision(
|
||||
state: &AppState,
|
||||
user_id: Uuid,
|
||||
role_key: &str,
|
||||
final_status: &str,
|
||||
rejection_reason: Option<&str>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let snapshot = load_role_profile_snapshot(state, user_id, role_key).await.unwrap_or_default();
|
||||
let approval_status = if final_status == "APPROVED" { "APPROVED" } else { "REJECTED" };
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO user_role_profiles (
|
||||
user_id, role_key, display_name, email, phone, location, status,
|
||||
verification_status, approval_status, rejection_reason, approved_at, verified_at
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7,
|
||||
$8, $9, $10,
|
||||
CASE WHEN $9 = 'APPROVED' THEN NOW() ELSE NULL END,
|
||||
CASE WHEN $8 = 'APPROVED' THEN NOW() ELSE NULL END
|
||||
)
|
||||
ON CONFLICT (user_id, role_key) DO UPDATE SET
|
||||
display_name = COALESCE(EXCLUDED.display_name, user_role_profiles.display_name),
|
||||
email = COALESCE(EXCLUDED.email, user_role_profiles.email),
|
||||
phone = COALESCE(EXCLUDED.phone, user_role_profiles.phone),
|
||||
location = COALESCE(EXCLUDED.location, user_role_profiles.location),
|
||||
status = EXCLUDED.status,
|
||||
verification_status = EXCLUDED.verification_status,
|
||||
approval_status = EXCLUDED.approval_status,
|
||||
rejection_reason = EXCLUDED.rejection_reason,
|
||||
approved_at = CASE
|
||||
WHEN EXCLUDED.approval_status = 'APPROVED' THEN NOW()
|
||||
ELSE user_role_profiles.approved_at
|
||||
END,
|
||||
verified_at = CASE
|
||||
WHEN EXCLUDED.verification_status = 'APPROVED' THEN NOW()
|
||||
ELSE user_role_profiles.verified_at
|
||||
END,
|
||||
updated_at = NOW()
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(role_key)
|
||||
.bind(snapshot.display_name)
|
||||
.bind(snapshot.email)
|
||||
.bind(snapshot.phone)
|
||||
.bind(snapshot.location)
|
||||
.bind(final_status)
|
||||
.bind(final_status)
|
||||
.bind(approval_status)
|
||||
.bind(rejection_reason)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -849,7 +849,7 @@ pub async fn verification_status(
|
|||
use sqlx::Row;
|
||||
let id: Uuid = r.try_get("id").unwrap_or(Uuid::nil());
|
||||
let reference_number: String = r.try_get("reference_number").unwrap_or_default();
|
||||
let status: String = r.try_get("status").unwrap_or_default();
|
||||
let raw_status: String = r.try_get("status").unwrap_or_default();
|
||||
let notes: Option<String> = r.try_get("notes").ok().flatten();
|
||||
let rejection_reason: Option<String> = r.try_get("rejection_reason").ok().flatten();
|
||||
let updated_at: Option<chrono::DateTime<chrono::Utc>> =
|
||||
|
|
@ -858,6 +858,39 @@ pub async fn verification_status(
|
|||
.try_get("documents")
|
||||
.unwrap_or(serde_json::Value::Array(vec![]));
|
||||
|
||||
let role_assignment_approved = if let Ok(role) = RoleRepository::get_by_key(&state.pool, &role_key).await {
|
||||
sqlx::query_scalar::<_, String>(
|
||||
r#"
|
||||
SELECT status
|
||||
FROM user_role_assignments
|
||||
WHERE user_id = $1 AND role_id = $2
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(auth.user_id)
|
||||
.bind(role.id)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|status| status.eq_ignore_ascii_case("APPROVED"))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let profile_status = fetch_current_profile_state(&state.pool, auth.user_id, &role_key).await.0;
|
||||
let status = if raw_status.eq_ignore_ascii_case("COMPLETED")
|
||||
|| role_assignment_approved
|
||||
|| profile_status.eq_ignore_ascii_case("APPROVED")
|
||||
{
|
||||
"APPROVED".to_string()
|
||||
} else if raw_status.eq_ignore_ascii_case("FINAL_REJECTED") {
|
||||
"REJECTED".to_string()
|
||||
} else {
|
||||
raw_status
|
||||
};
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(serde_json::json!({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue