// src/auth/handlers/register.rs use bcrypt::{hash, DEFAULT_COST}; use tonic::{Response, Status}; use common::proto::komp_ac::auth::{RegisterRequest, AuthResponse}; use crate::db::PgPool; use crate::auth::models::AuthError; const DEFAULT_ROLE: &str = "accountant"; pub async fn register( pool: &PgPool, payload: RegisterRequest, ) -> Result, Status> { // Validate required fields if payload.email.trim().is_empty() { return Err(Status::invalid_argument("Email is required")); } // Validate passwords match if payload.password != payload.password_confirmation { return Err(Status::invalid_argument(AuthError::PasswordMismatch.to_string())); } // Hash password let password_hash = hash(payload.password, DEFAULT_COST) .map_err(|e| Status::internal(AuthError::HashingError(e.to_string()).to_string()))?; let role_to_insert = if payload.role.is_empty() { DEFAULT_ROLE } else { &payload.role }; // Insert user let user = sqlx::query!( r#" INSERT INTO users (username, email, password_hash, role) VALUES ($1, $2, $3, $4) RETURNING id, username, email, role "#, payload.username, payload.email, password_hash, role_to_insert ) .fetch_one(pool) .await .map_err(|e| { if let Some(db_err) = e.as_database_error() { if db_err.constraint() == Some("valid_roles") { return Status::invalid_argument(format!("Invalid role specified: '{}'", role_to_insert)); } // Check for specific constraint violations if let Some(constraint) = db_err.constraint() { if constraint.contains("users_username_key") { return Status::already_exists("Username already exists".to_string()); } if constraint.contains("users_email_key") { return Status::already_exists("Email already exists".to_string()); } } } if e.to_string().contains("duplicate key") { Status::already_exists(AuthError::UserExists.to_string()) } else { Status::internal(AuthError::DatabaseError(e.to_string()).to_string()) } })?; Ok(Response::new(AuthResponse { id: user.id.to_string(), username: user.username, email: user.email.unwrap_or_default(), role: user.role, })) }