use sea_orm::*;
use anyhow::{Context, Result};
use chrono::Utc;
use uuid::Uuid;
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 {
id: Set(Uuid::now_v7()),
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),
};
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: Uuid,
) -> 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: Uuid,
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: Uuid,
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: Uuid) -> 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(())
}
⭐
Log in to mark this repository.
6.6 KiB · text
History
94679c7