diff --git a/apps/users/src/handlers/admin.rs b/apps/users/src/handlers/admin.rs index 68676bc..89479e7 100644 --- a/apps/users/src/handlers/admin.rs +++ b/apps/users/src/handlers/admin.rs @@ -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 "#; diff --git a/apps/users/src/handlers/approvals.rs b/apps/users/src/handlers/approvals.rs index bba409e..d721358 100644 --- a/apps/users/src/handlers/approvals.rs +++ b/apps/users/src/handlers/approvals.rs @@ -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) diff --git a/apps/users/src/handlers/auth.rs b/apps/users/src/handlers/auth.rs index a98b9bf..377aad7 100644 --- a/apps/users/src/handlers/auth.rs +++ b/apps/users/src/handlers/auth.rs @@ -35,7 +35,8 @@ pub fn router() -> Router { #[derive(Deserialize)] pub struct RegisterPayload { - pub full_name: String, + pub first_name: String, + pub last_name: String, pub email: String, pub phone: Option, pub password: String, @@ -91,7 +92,7 @@ pub struct RegisterResponse { pub user_id: String, pub email: String, pub phone: Option, - 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, pub active_role: Option, @@ -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" })))) } diff --git a/apps/users/src/handlers/config.rs b/apps/users/src/handlers/config.rs index 6171a04..c782e50 100644 --- a/apps/users/src/handlers/config.rs +++ b/apps/users/src/handlers/config.rs @@ -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, diff --git a/apps/users/src/handlers/dashboard.rs b/apps/users/src/handlers/dashboard.rs index 04682d3..c3fc52f 100644 --- a/apps/users/src/handlers/dashboard.rs +++ b/apps/users/src/handlers/dashboard.rs @@ -125,7 +125,7 @@ async fn get_metrics(State(state): State) -> Json( 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') diff --git a/apps/users/src/handlers/settings.rs b/apps/users/src/handlers/settings.rs index c1a457f..c991e9b 100644 --- a/apps/users/src/handlers/settings.rs +++ b/apps/users/src/handlers/settings.rs @@ -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( diff --git a/apps/users/src/handlers/support.rs b/apps/users/src/handlers/support.rs index e8fb858..4e75ff1 100644 --- a/apps/users/src/handlers/support.rs +++ b/apps/users/src/handlers/support.rs @@ -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() }; diff --git a/apps/users/src/handlers/verifications.rs b/apps/users/src/handlers/verifications.rs index e4fad13..c861803 100644 --- a/apps/users/src/handlers/verifications.rs +++ b/apps/users/src/handlers/verifications.rs @@ -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; diff --git a/crates/db/src/models/user.rs b/crates/db/src/models/user.rs index ce4edad..d7a31bd 100644 --- a/crates/db/src/models/user.rs +++ b/crates/db/src/models/user.rs @@ -10,7 +10,7 @@ pub struct User { pub id: Uuid, pub email: String, pub password_hash: String, - pub full_name: Option, + pub name: Option, pub phone: Option, 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, pub password_hash: String, @@ -51,17 +51,17 @@ impl UserRepository { pub async fn create(pool: &PgPool, payload: CreateUserPayload) -> Result { 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 { 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 { 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 { 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 { 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,