fix(auth): use 'name' column instead of 'full_name', combine first_name + last_name
- Replace full_name with name in User struct and all queries - RegisterPayload now takes first_name + last_name instead of full_name - Combine first_name and last_name into name before saving to DB - Update all response structs to use 'name' field instead of 'full_name' - Fix support and dashboard queries to use u.name instead of u.full_name Root cause: DB has 'name' column, code was using 'full_name' which doesn't exist.
This commit is contained in:
parent
f5130569e5
commit
231ff9530f
9 changed files with 50 additions and 47 deletions
|
|
@ -49,12 +49,12 @@ async fn list_users(
|
|||
// Generic list: users + their approved roles
|
||||
r#"
|
||||
SELECT
|
||||
u.id, u.email, u.full_name, u.status, u.created_at,
|
||||
u.id, u.email, u.name, u.status, u.created_at,
|
||||
COALESCE(array_agg(r.key) FILTER (WHERE r.key IS NOT NULL), '{}') as roles
|
||||
FROM users u
|
||||
LEFT JOIN user_roles ur ON ur.user_id = u.id AND ur.status = 'APPROVED'
|
||||
LEFT JOIN roles r ON r.id = ur.role_id
|
||||
WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
WHERE ($1 = '' OR LOWER(u.name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
GROUP BY u.id
|
||||
ORDER BY u.created_at DESC
|
||||
LIMIT 100
|
||||
|
|
@ -80,11 +80,11 @@ async fn list_users(
|
|||
format!(
|
||||
r#"
|
||||
SELECT
|
||||
u.id, u.email, u.full_name, p.status, u.created_at,
|
||||
u.id, u.email, u.name, p.status, u.created_at,
|
||||
ARRAY['{}']::text[] as roles
|
||||
FROM users u
|
||||
JOIN {} p ON p.user_id = u.id
|
||||
WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
WHERE ($1 = '' OR LOWER(u.name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
ORDER BY u.created_at DESC
|
||||
LIMIT 100
|
||||
"#,
|
||||
|
|
@ -110,12 +110,12 @@ async fn list_customers(
|
|||
|
||||
let sql = r#"
|
||||
SELECT
|
||||
u.id, u.email, u.full_name, u.status, u.created_at,
|
||||
u.id, u.email, u.name, u.status, u.created_at,
|
||||
ARRAY['CUSTOMER']::text[] as roles
|
||||
FROM users u
|
||||
JOIN user_roles ur ON ur.user_id = u.id AND ur.status = 'APPROVED'
|
||||
JOIN roles r ON r.id = ur.role_id AND r.key = 'CUSTOMER'
|
||||
WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
WHERE ($1 = '' OR LOWER(u.name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
ORDER BY u.created_at DESC
|
||||
LIMIT 50
|
||||
"#;
|
||||
|
|
@ -138,12 +138,12 @@ async fn list_candidates(
|
|||
|
||||
let sql = r#"
|
||||
SELECT
|
||||
u.id, u.email, u.full_name, u.status, u.created_at,
|
||||
u.id, u.email, u.name, u.status, u.created_at,
|
||||
ARRAY['JOB_SEEKER']::text[] as roles
|
||||
FROM users u
|
||||
JOIN user_roles ur ON ur.user_id = u.id AND ur.status = 'APPROVED'
|
||||
JOIN roles r ON r.id = ur.role_id AND r.key = 'JOB_SEEKER'
|
||||
WHERE ($1 = '' OR LOWER(u.full_name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
WHERE ($1 = '' OR LOWER(u.name) LIKE '%' || $1 || '%' OR LOWER(u.email) LIKE '%' || $1 || '%')
|
||||
ORDER BY u.created_at DESC
|
||||
LIMIT 50
|
||||
"#;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ async fn get_submission(
|
|||
Json(serde_json::json!({
|
||||
"user": {
|
||||
"id": user.id,
|
||||
"name": user.full_name,
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"phone": user.phone,
|
||||
"status": user.status,
|
||||
|
|
@ -247,7 +247,7 @@ async fn activate_profile_after_final_approval(
|
|||
.mail
|
||||
.send_approval_approved_email(
|
||||
&user.email,
|
||||
user.full_name.as_deref().unwrap_or_default(),
|
||||
user.name.as_deref().unwrap_or_default(),
|
||||
&display,
|
||||
)
|
||||
.await;
|
||||
|
|
@ -303,7 +303,7 @@ async fn reject_profile_after_final_approval(
|
|||
.mail
|
||||
.send_approval_rejected_email(
|
||||
&user.email,
|
||||
user.full_name.as_deref().unwrap_or_default(),
|
||||
user.name.as_deref().unwrap_or_default(),
|
||||
&display,
|
||||
reason.unwrap_or("Rejected by final approval"),
|
||||
)
|
||||
|
|
@ -440,7 +440,7 @@ async fn approve_job(
|
|||
.await;
|
||||
|
||||
let company_info = sqlx::query_as::<_, (String, String)>(
|
||||
"SELECT u.full_name, u.email FROM companies c JOIN users u ON u.id = c.user_id WHERE c.id = $1",
|
||||
"SELECT u.name, u.email FROM companies c JOIN users u ON u.id = c.user_id WHERE c.id = $1",
|
||||
)
|
||||
.bind(existing.company_id)
|
||||
.fetch_optional(&state.pool)
|
||||
|
|
@ -490,7 +490,7 @@ async fn reject_job(
|
|||
.await;
|
||||
|
||||
let company_info = sqlx::query_as::<_, (String, String)>(
|
||||
"SELECT u.full_name, u.email FROM companies c JOIN users u ON u.id = c.user_id WHERE c.id = $1",
|
||||
"SELECT u.name, u.email FROM companies c JOIN users u ON u.id = c.user_id WHERE c.id = $1",
|
||||
)
|
||||
.bind(existing.company_id)
|
||||
.fetch_optional(&state.pool)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ pub fn router() -> Router<AppState> {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RegisterPayload {
|
||||
pub full_name: String,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub password: String,
|
||||
|
|
@ -91,7 +92,7 @@ pub struct RegisterResponse {
|
|||
pub user_id: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub full_name: String,
|
||||
pub name: String,
|
||||
pub status: String,
|
||||
pub email_verified: bool,
|
||||
pub created_at: String,
|
||||
|
|
@ -101,7 +102,7 @@ pub struct RegisterResponse {
|
|||
pub struct SessionUser {
|
||||
pub id: String,
|
||||
pub email: String,
|
||||
pub full_name: String,
|
||||
pub name: String,
|
||||
pub email_verified: bool,
|
||||
pub roles: Vec<String>,
|
||||
pub active_role: Option<String>,
|
||||
|
|
@ -197,10 +198,12 @@ async fn register(
|
|||
let password_hash = hash_password(&payload.password)
|
||||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "INTERNAL_ERROR"))?;
|
||||
|
||||
let full_name = format!("{} {}", payload.first_name.trim(), payload.last_name.trim()).trim().to_string();
|
||||
|
||||
let user = UserRepository::create(&state.pool, CreateUserPayload {
|
||||
full_name: payload.full_name,
|
||||
email: email.clone(),
|
||||
phone: payload.phone.filter(|p| !p.trim().is_empty()),
|
||||
name: full_name,
|
||||
email: email.clone(),
|
||||
phone: payload.phone.filter(|p| !p.trim().is_empty()),
|
||||
password_hash,
|
||||
})
|
||||
.await
|
||||
|
|
@ -252,13 +255,13 @@ async fn register(
|
|||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "CACHE_ERROR"))?;
|
||||
cache::otp::record_resend(&mut redis, &user.id.to_string()).await.ok();
|
||||
|
||||
let _ = state.mail.send_verification_email(&user.email, &user.full_name.clone().unwrap_or_default(), &otp).await;
|
||||
let _ = state.mail.send_verification_email(&user.email, &user.name.clone().unwrap_or_default(), &otp).await;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(RegisterResponse {
|
||||
user_id: user.id.to_string(),
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
full_name: user.full_name.unwrap_or_default(),
|
||||
name: user.name.unwrap_or_default(),
|
||||
status: user.status,
|
||||
email_verified: user.email_verified,
|
||||
created_at: user.created_at.to_rfc3339(),
|
||||
|
|
@ -327,7 +330,7 @@ async fn login(
|
|||
"user": {
|
||||
"id": user.id.to_string(),
|
||||
"email": user.email,
|
||||
"full_name": user.full_name.unwrap_or_default(),
|
||||
"full_name": user.name.unwrap_or_default(),
|
||||
"email_verified": user.email_verified,
|
||||
"active_role": active_role,
|
||||
"roles": user_roles,
|
||||
|
|
@ -439,7 +442,7 @@ async fn session(
|
|||
Ok(Json(SessionUser {
|
||||
id: user.id.to_string(),
|
||||
email: user.email,
|
||||
full_name: user.full_name.unwrap_or_default(),
|
||||
name: user.name.unwrap_or_default(),
|
||||
email_verified: user.email_verified,
|
||||
active_role: user_roles.first().cloned(),
|
||||
roles: user_roles,
|
||||
|
|
@ -469,7 +472,7 @@ async fn verify_email(
|
|||
|
||||
// Get user details for welcome email
|
||||
if let Ok(user) = UserRepository::get_by_id(&state.pool, user_id).await {
|
||||
let _ = state.mail.send_welcome_email(&user.email, &user.full_name.unwrap_or_default()).await;
|
||||
let _ = state.mail.send_welcome_email(&user.email, &user.name.unwrap_or_default()).await;
|
||||
}
|
||||
|
||||
Ok((StatusCode::OK, Json(serde_json::json!({ "message": "Email verified successfully" }))))
|
||||
|
|
@ -505,7 +508,7 @@ async fn resend_otp(
|
|||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "CACHE_ERROR"))?;
|
||||
cache::otp::record_resend(&mut redis, &user.id.to_string()).await.ok();
|
||||
|
||||
let _ = state.mail.send_verification_email(&user.email, &user.full_name.unwrap_or_default(), &otp).await;
|
||||
let _ = state.mail.send_verification_email(&user.email, &user.name.unwrap_or_default(), &otp).await;
|
||||
|
||||
Ok(silent_ok)
|
||||
}
|
||||
|
|
@ -530,7 +533,7 @@ async fn forgot_password(
|
|||
.await
|
||||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "CACHE_ERROR"))?;
|
||||
|
||||
let _ = state.mail.send_password_reset_email(&user.email, &user.full_name.unwrap_or_default(), &token).await;
|
||||
let _ = state.mail.send_password_reset_email(&user.email, &user.name.unwrap_or_default(), &token).await;
|
||||
|
||||
Ok(silent_ok)
|
||||
}
|
||||
|
|
@ -564,7 +567,7 @@ async fn reset_password(
|
|||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "DB_ERROR"))?;
|
||||
|
||||
if let Ok(user) = UserRepository::get_by_id(&state.pool, user_id).await {
|
||||
let _ = state.mail.send_password_changed_email(&user.email, user.full_name.as_deref().unwrap_or_default()).await;
|
||||
let _ = state.mail.send_password_changed_email(&user.email, user.name.as_deref().unwrap_or_default()).await;
|
||||
}
|
||||
|
||||
Ok((StatusCode::OK, Json(serde_json::json!({ "message": "Password reset successfully" }))))
|
||||
|
|
@ -597,7 +600,7 @@ async fn change_password(
|
|||
.await
|
||||
.map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string(), "DB_ERROR"))?;
|
||||
|
||||
let _ = state.mail.send_password_changed_email(&user.email, user.full_name.as_deref().unwrap_or_default()).await;
|
||||
let _ = state.mail.send_password_changed_email(&user.email, user.name.as_deref().unwrap_or_default()).await;
|
||||
|
||||
Ok((StatusCode::OK, Json(serde_json::json!({ "message": "Password changed successfully" }))))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ async fn get_my_runtime_config(
|
|||
"user".to_string(),
|
||||
serde_json::json!({
|
||||
"id": user.id.to_string(),
|
||||
"full_name": user.full_name.unwrap_or_default(),
|
||||
"name": user.name.unwrap_or_default(),
|
||||
"email": user.email,
|
||||
"roles": roles,
|
||||
"active_role": role_key,
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ async fn get_metrics(State(state): State<crate::AppState>) -> Json<DashboardMetr
|
|||
let recent_leads = sqlx::query_as::<_, LeadRow>(
|
||||
r#"
|
||||
SELECT r.id, r.title, r.status, r.created_at,
|
||||
u.full_name AS requester_name
|
||||
u.name AS requester_name
|
||||
FROM leads r
|
||||
LEFT JOIN users u ON u.id = r.created_by_user_id
|
||||
WHERE r.status IN ('PENDING_APPROVAL', 'APPROVED')
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ async fn create_delete_account_request(
|
|||
.mail
|
||||
.send_account_deleted_email(
|
||||
&user.email,
|
||||
user.full_name.as_deref().unwrap_or_default(),
|
||||
user.name.as_deref().unwrap_or_default(),
|
||||
)
|
||||
.await;
|
||||
let _ = sqlx::query(
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ async fn user_create_ticket(
|
|||
};
|
||||
let _ = state.mail.send_support_ticket_created_email(
|
||||
&user.email,
|
||||
user.full_name.as_deref().unwrap_or_default(),
|
||||
user.name.as_deref().unwrap_or_default(),
|
||||
&r.id.to_string(),
|
||||
&body.subject,
|
||||
&category,
|
||||
|
|
@ -444,7 +444,7 @@ async fn admin_list_cases(
|
|||
t.id, t.subject, t.description, t.category, t.priority, t.status,
|
||||
t.requester_name, t.requester_email, t.assigned_to,
|
||||
t.created_at, t.updated_at,
|
||||
u.full_name AS user_name, u.email AS user_email
|
||||
u.name AS user_name, u.email AS user_email
|
||||
FROM support_tickets t
|
||||
LEFT JOIN users u ON u.id = t.user_id
|
||||
WHERE ($1 = '' OR t.status = $1)
|
||||
|
|
@ -586,7 +586,7 @@ async fn admin_get_case(
|
|||
t.id, t.subject, t.description, t.category, t.priority, t.status,
|
||||
t.requester_name, t.requester_email, t.assigned_to,
|
||||
t.created_at, t.updated_at,
|
||||
u.full_name AS user_name, u.email AS user_email
|
||||
u.name AS user_name, u.email AS user_email
|
||||
FROM support_tickets t
|
||||
LEFT JOIN users u ON u.id = t.user_id
|
||||
WHERE t.id = $1
|
||||
|
|
@ -832,7 +832,7 @@ async fn admin_add_message(
|
|||
if let Some(user_email) = ticket.requester_email {
|
||||
// Try to get user name from user table
|
||||
let user_name = if let Ok(user) = db::models::user::UserRepository::get_by_email(&state.pool, &user_email).await {
|
||||
user.full_name.unwrap_or_default()
|
||||
user.name.unwrap_or_default()
|
||||
} else {
|
||||
ticket.requester_name.unwrap_or_default()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ async fn trigger_rejection(
|
|||
let display = role_key_to_display(&role_key);
|
||||
let _ = state.mail.send_approval_rejected_email(
|
||||
&user.email,
|
||||
user.full_name.as_deref().unwrap_or_default(),
|
||||
user.name.as_deref().unwrap_or_default(),
|
||||
&display,
|
||||
reason_str
|
||||
).await;
|
||||
|
|
@ -182,7 +182,7 @@ async fn approve_verification(
|
|||
let display = role_key_to_display(&v.role_key);
|
||||
let _ = state.mail.send_approval_approved_email(
|
||||
&user.email,
|
||||
user.full_name.as_deref().unwrap_or_default(),
|
||||
user.name.as_deref().unwrap_or_default(),
|
||||
&display
|
||||
).await;
|
||||
}
|
||||
|
|
@ -296,7 +296,7 @@ async fn request_documents(
|
|||
let display = role_key_to_display(&v.role_key);
|
||||
let _ = state.mail.send_documents_requested_email(
|
||||
&user.email,
|
||||
user.full_name.as_deref().unwrap_or_default(),
|
||||
user.name.as_deref().unwrap_or_default(),
|
||||
&display,
|
||||
&payload.message
|
||||
).await;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub struct User {
|
|||
pub id: Uuid,
|
||||
pub email: String,
|
||||
pub password_hash: String,
|
||||
pub full_name: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub phone: Option<String>,
|
||||
pub email_verified: bool,
|
||||
pub phone_verified: bool,
|
||||
|
|
@ -27,7 +27,7 @@ pub struct User {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CreateUserPayload {
|
||||
pub full_name: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub password_hash: String,
|
||||
|
|
@ -51,17 +51,17 @@ impl UserRepository {
|
|||
pub async fn create(pool: &PgPool, payload: CreateUserPayload) -> Result<User, sqlx::Error> {
|
||||
let user = sqlx::query_as::<_, User>(
|
||||
r#"
|
||||
INSERT INTO users (full_name, email, phone, password_hash, email_verified, phone_verified)
|
||||
INSERT INTO users (name, email, phone, password_hash, email_verified, phone_verified)
|
||||
VALUES ($1, $2, $3, $4, false, false)
|
||||
RETURNING
|
||||
id, email, password_hash, full_name, phone,
|
||||
id, email, password_hash, name, phone,
|
||||
email_verified, phone_verified, status,
|
||||
email_verification_token, email_verification_expires_at,
|
||||
reset_password_token, reset_password_expires_at,
|
||||
created_at, updated_at, deleted_at
|
||||
"#,
|
||||
)
|
||||
.bind(payload.full_name)
|
||||
.bind(&payload.name)
|
||||
.bind(payload.email.to_lowercase())
|
||||
.bind(payload.phone)
|
||||
.bind(payload.password_hash)
|
||||
|
|
@ -74,7 +74,7 @@ impl UserRepository {
|
|||
pub async fn get_by_email(pool: &PgPool, email: &str) -> Result<User, sqlx::Error> {
|
||||
sqlx::query_as::<_, User>(
|
||||
r#"
|
||||
SELECT id, email, password_hash, full_name, phone,
|
||||
SELECT id, email, password_hash, name, phone,
|
||||
email_verified, phone_verified, status,
|
||||
email_verification_token, email_verification_expires_at,
|
||||
reset_password_token, reset_password_expires_at,
|
||||
|
|
@ -91,7 +91,7 @@ impl UserRepository {
|
|||
pub async fn get_by_id(pool: &PgPool, id: Uuid) -> Result<User, sqlx::Error> {
|
||||
sqlx::query_as::<_, User>(
|
||||
r#"
|
||||
SELECT id, email, password_hash, full_name, phone,
|
||||
SELECT id, email, password_hash, name, phone,
|
||||
email_verified, phone_verified, status,
|
||||
email_verification_token, email_verification_expires_at,
|
||||
reset_password_token, reset_password_expires_at,
|
||||
|
|
@ -148,7 +148,7 @@ impl UserRepository {
|
|||
pub async fn get_by_verification_token(pool: &PgPool, token: &str) -> Result<User, sqlx::Error> {
|
||||
sqlx::query_as::<_, User>(
|
||||
r#"
|
||||
SELECT id, email, password_hash, full_name, phone,
|
||||
SELECT id, email, password_hash, name, phone,
|
||||
email_verified, phone_verified, status,
|
||||
email_verification_token, email_verification_expires_at,
|
||||
reset_password_token, reset_password_expires_at,
|
||||
|
|
@ -196,7 +196,7 @@ impl UserRepository {
|
|||
pub async fn get_by_reset_token(pool: &PgPool, token: &str) -> Result<User, sqlx::Error> {
|
||||
sqlx::query_as::<_, User>(
|
||||
r#"
|
||||
SELECT id, email, password_hash, full_name, phone,
|
||||
SELECT id, email, password_hash, name, phone,
|
||||
email_verified, phone_verified, status,
|
||||
email_verification_token, email_verification_expires_at,
|
||||
reset_password_token, reset_password_expires_at,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue