exercises

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

mod.rs (3049B)


      1 use anyhow::Result;
      2 use bcrypt::{hash, verify, DEFAULT_COST};
      3 use chrono::{Duration, Utc};
      4 use sqlx::PgPool;
      5 
      6 use crate::models::{User, UserSession, RegisterRequest, LoginRequest};
      7 
      8 pub async fn register_user(
      9     pool: &PgPool,
     10     req: RegisterRequest,
     11 ) -> Result<User> {
     12     // Hash password
     13     let password_hash = hash(&req.password, DEFAULT_COST)?;
     14 
     15     // Insert user
     16     let user = sqlx::query_as::<_, User>(
     17         r#"
     18         INSERT INTO users (email, password_hash, first_name, last_name, phone)
     19         VALUES ($1, $2, $3, $4, $5)
     20         RETURNING *
     21         "#
     22     )
     23     .bind(&req.email)
     24     .bind(&password_hash)
     25     .bind(&req.first_name)
     26     .bind(&req.last_name)
     27     .bind(&req.phone)
     28     .fetch_one(pool)
     29     .await?;
     30 
     31     Ok(user)
     32 }
     33 
     34 pub async fn login_user(
     35     pool: &PgPool,
     36     req: LoginRequest,
     37 ) -> Result<(User, UserSession)> {
     38     // Find user by email
     39     let user = sqlx::query_as::<_, User>(
     40         "SELECT * FROM users WHERE email = $1 AND is_active = true"
     41     )
     42     .bind(&req.email)
     43     .fetch_optional(pool)
     44     .await?
     45     .ok_or_else(|| anyhow::anyhow!("Invalid email or password"))?;
     46 
     47     // Verify password
     48     let password_valid = verify(&req.password, &user.password_hash)?;
     49     if !password_valid {
     50         return Err(anyhow::anyhow!("Invalid email or password"));
     51     }
     52 
     53     // Generate session token
     54     let session_token = generate_session_token();
     55     let expires_at = Utc::now() + Duration::days(7);
     56 
     57     // Create session
     58     let session = sqlx::query_as::<_, UserSession>(
     59         r#"
     60         INSERT INTO user_sessions (user_id, session_token, expires_at)
     61         VALUES ($1, $2, $3)
     62         RETURNING *
     63         "#
     64     )
     65     .bind(user.id)
     66     .bind(&session_token)
     67     .bind(expires_at)
     68     .fetch_one(pool)
     69     .await?;
     70 
     71     // Update last login
     72     sqlx::query("UPDATE users SET last_login_at = NOW() WHERE id = $1")
     73         .bind(user.id)
     74         .execute(pool)
     75         .await?;
     76 
     77     Ok((user, session))
     78 }
     79 
     80 pub async fn logout_user(pool: &PgPool, session_token: &str) -> Result<bool> {
     81     let result = sqlx::query(
     82         "UPDATE user_sessions SET is_active = false WHERE session_token = $1"
     83     )
     84     .bind(session_token)
     85     .execute(pool)
     86     .await?;
     87 
     88     Ok(result.rows_affected() > 0)
     89 }
     90 
     91 pub async fn validate_session(pool: &PgPool, session_token: &str) -> Result<Option<User>> {
     92     let session = sqlx::query_as::<_, UserSession>(
     93         r#"
     94         SELECT * FROM user_sessions
     95         WHERE session_token = $1
     96           AND is_active = true
     97           AND expires_at > NOW()
     98         "#
     99     )
    100     .bind(session_token)
    101     .fetch_optional(pool)
    102     .await?;
    103 
    104     if let Some(session) = session {
    105         let user = sqlx::query_as::<_, User>(
    106             "SELECT * FROM users WHERE id = $1 AND is_active = true"
    107         )
    108         .bind(session.user_id)
    109         .fetch_optional(pool)
    110         .await?;
    111 
    112         Ok(user)
    113     } else {
    114         Ok(None)
    115     }
    116 }
    117 
    118 fn generate_session_token() -> String {
    119     use uuid::Uuid;
    120     format!("SES-{}-{}", Utc::now().timestamp(), Uuid::new_v4())
    121 }