6.6 KiB · text History 1ce9641
use sea_orm::*;
use anyhow::{Context, Result};
use chrono::Utc;
use crate::entities::{users, user_sessions};
use crate::auth::{password, tokens};

#[derive(Debug)]
pub struct CreateUserRequest {
    pub username: String,
    pub email: String,
    pub password: String,
    pub registration_token: String,
}

#[derive(Debug)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

/// Create a new user with registration token validation
pub async fn create_user(
    db: &DatabaseConnection,
    request: CreateUserRequest,
) -> Result<users::Model> {
    // Validate registration token
    let reg_token = tokens::validate_registration_token(db, &request.registration_token, &request.email)
        .await
        .context("Failed to validate registration token")?;

    let reg_token = match reg_token {
        Some(token) => token,
        None => return Err(anyhow::anyhow!("Invalid or expired registration token")),
    };

    // Check if username or email already exists
    let existing_user = users::Entity::find()
        .filter(
            Condition::any()
                .add(users::Column::Username.eq(&request.username))
                .add(users::Column::Email.eq(&request.email))
        )
        .one(db)
        .await
        .context("Failed to check existing users")?;

    if existing_user.is_some() {
        return Err(anyhow::anyhow!("Username or email already exists"));
    }

    // Hash password
    let password_hash = password::hash_password(&request.password)
        .context("Failed to hash password")?;

    // Create user
    let new_user = users::ActiveModel {
        username: Set(request.username),
        email: Set(request.email),
        password_hash: Set(password_hash),
        is_active: Set(true),
        created_at: Set(Utc::now().into()),
        updated_at: Set(Utc::now().into()),
        last_login_at: Set(None),
        ..Default::default()
    };

    let user = new_user
        .insert(db)
        .await
        .context("Failed to create user")?;

    // Mark registration token as used
    tokens::mark_registration_token_used(db, reg_token.id, user.id)
        .await
        .context("Failed to mark registration token as used")?;

    Ok(user)
}

/// Authenticate user and return user if successful
pub async fn authenticate_user(
    db: &DatabaseConnection,
    request: LoginRequest,
) -> Result<Option<users::Model>> {
    let user = users::Entity::find()
        .filter(users::Column::Username.eq(&request.username))
        .filter(users::Column::IsActive.eq(true))
        .one(db)
        .await
        .context("Failed to query user")?;

    if let Some(mut user) = user {
        let password_valid = password::verify_password(&request.password, &user.password_hash)
            .context("Failed to verify password")?;

        if password_valid {
            // Update last login time
            let mut user_update: users::ActiveModel = user.clone().into();
            user_update.last_login_at = Set(Some(Utc::now().into()));
            user = user_update.update(db).await.context("Failed to update last login")?;

            Ok(Some(user))
        } else {
            Ok(None)
        }
    } else {
        Ok(None)
    }
}

/// Get user by ID
pub async fn get_user_by_id(
    db: &DatabaseConnection,
    user_id: i64,
) -> Result<Option<users::Model>> {
    users::Entity::find_by_id(user_id)
        .one(db)
        .await
        .context("Failed to query user")
}

/// Update user information
pub async fn update_user(
    db: &DatabaseConnection,
    user_id: i64,
    username: Option<String>,
    email: Option<String>,
) -> Result<users::Model> {
    let user = get_user_by_id(db, user_id)
        .await?
        .ok_or_else(|| anyhow::anyhow!("User not found"))?;

    let mut user_update: users::ActiveModel = user.into();
    user_update.updated_at = Set(Utc::now().into());

    if let Some(username) = username {
        // Check if username is already taken by another user
        let existing = users::Entity::find()
            .filter(users::Column::Username.eq(&username))
            .filter(users::Column::Id.ne(user_id))
            .one(db)
            .await
            .context("Failed to check username availability")?;

        if existing.is_some() {
            return Err(anyhow::anyhow!("Username already taken"));
        }

        user_update.username = Set(username);
    }

    if let Some(email) = email {
        // Check if email is already taken by another user
        let existing = users::Entity::find()
            .filter(users::Column::Email.eq(&email))
            .filter(users::Column::Id.ne(user_id))
            .one(db)
            .await
            .context("Failed to check email availability")?;

        if existing.is_some() {
            return Err(anyhow::anyhow!("Email already taken"));
        }

        user_update.email = Set(email);
    }

    user_update
        .update(db)
        .await
        .context("Failed to update user")
}

/// Change user password
pub async fn change_password(
    db: &DatabaseConnection,
    user_id: i64,
    old_password: &str,
    new_password: &str,
) -> Result<()> {
    let user = get_user_by_id(db, user_id)
        .await?
        .ok_or_else(|| anyhow::anyhow!("User not found"))?;

    // Verify old password
    let old_password_valid = password::verify_password(old_password, &user.password_hash)
        .context("Failed to verify old password")?;

    if !old_password_valid {
        return Err(anyhow::anyhow!("Invalid old password"));
    }

    // Hash new password
    let new_password_hash = password::hash_password(new_password)
        .context("Failed to hash new password")?;

    // Update user
    let mut user_update: users::ActiveModel = user.into();
    user_update.password_hash = Set(new_password_hash);
    user_update.updated_at = Set(Utc::now().into());

    user_update
        .update(db)
        .await
        .context("Failed to update password")?;

    Ok(())
}

/// Deactivate user account
pub async fn deactivate_user(db: &DatabaseConnection, user_id: i64) -> Result<()> {
    let user = get_user_by_id(db, user_id)
        .await?
        .ok_or_else(|| anyhow::anyhow!("User not found"))?;

    let mut user_update: users::ActiveModel = user.into();
    user_update.is_active = Set(false);
    user_update.updated_at = Set(Utc::now().into());

    user_update
        .update(db)
        .await
        .context("Failed to deactivate user")?;

    // Also delete all active sessions
    user_sessions::Entity::delete_many()
        .filter(user_sessions::Column::UserId.eq(user_id))
        .exec(db)
        .await
        .context("Failed to delete user sessions")?;

    Ok(())
}