use serde::{Deserialize, Serialize};
use std::time::SystemTime;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct User {
pub id: i64,
pub username: String,
pub email: String,
#[serde(skip_serializing)]
pub password_hash: String,
pub full_name: Option<String>,
pub bio: Option<String>,
pub created_at: SystemTime,
pub updated_at: SystemTime,
pub is_active: bool,
pub is_admin: bool,
pub email_verified: bool,
}
impl User {
pub fn new(
id: i64,
username: String,
email: String,
password_hash: String,
) -> Result<Self, ValidationError> {
let now = SystemTime::now();
let user = Self {
id,
username,
email,
password_hash,
full_name: None,
bio: None,
created_at: now,
updated_at: now,
is_active: true,
is_admin: false,
email_verified: false,
};
user.validate()?;
Ok(user)
}
pub fn validate(&self) -> Result<(), ValidationError> {
if self.username.len() < 3 {
return Err(ValidationError::UsernameTooShort);
}
if self.username.len() > 50 {
return Err(ValidationError::UsernameTooLong);
}
if !self.username.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
return Err(ValidationError::InvalidUsernameFormat);
}
if !self.email.contains('@') {
return Err(ValidationError::InvalidEmailFormat);
}
if self.email.len() > 255 {
return Err(ValidationError::EmailTooLong);
}
if self.password_hash.is_empty() {
return Err(ValidationError::PasswordHashRequired);
}
if let Some(bio) = &self.bio {
if bio.len() > 500 {
return Err(ValidationError::BioTooLong);
}
}
Ok(())
}
pub fn set_full_name(&mut self, name: String) {
self.full_name = Some(name);
self.updated_at = SystemTime::now();
}
pub fn set_bio(&mut self, bio: String) -> Result<(), ValidationError> {
if bio.len() > 500 {
return Err(ValidationError::BioTooLong);
}
self.bio = Some(bio);
self.updated_at = SystemTime::now();
Ok(())
}
pub fn verify_email(&mut self) {
self.email_verified = true;
self.updated_at = SystemTime::now();
}
pub fn deactivate(&mut self) {
self.is_active = false;
self.updated_at = SystemTime::now();
}
pub fn activate(&mut self) {
self.is_active = true;
self.updated_at = SystemTime::now();
}
pub fn to_public(&self) -> PublicUser {
PublicUser {
id: self.id,
username: self.username.clone(),
full_name: self.full_name.clone(),
bio: self.bio.clone(),
created_at: self.created_at,
is_active: self.is_active,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicUser {
pub id: i64,
pub username: String,
pub full_name: Option<String>,
pub bio: Option<String>,
pub created_at: SystemTime,
pub is_active: bool,
}
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub email: String,
pub password: String,
pub full_name: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateUserRequest {
pub full_name: Option<String>,
pub bio: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ValidationError {
UsernameTooShort,
UsernameTooLong,
InvalidUsernameFormat,
InvalidEmailFormat,
EmailTooLong,
PasswordHashRequired,
BioTooLong,
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UsernameTooShort => write!(f, "Username must be at least 3 characters"),
Self::UsernameTooLong => write!(f, "Username must be at most 50 characters"),
Self::InvalidUsernameFormat => write!(f, "Username can only contain alphanumeric characters, underscores, and hyphens"),
Self::InvalidEmailFormat => write!(f, "Invalid email format"),
Self::EmailTooLong => write!(f, "Email must be at most 255 characters"),
Self::PasswordHashRequired => write!(f, "Password hash is required"),
Self::BioTooLong => write!(f, "Bio must be at most 500 characters"),
}
}
}
impl std::error::Error for ValidationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_user() {
let user = User::new(
1,
"testuser".to_string(),
"test@example.com".to_string(),
"hashed_password".to_string(),
).unwrap();
assert_eq!(user.username, "testuser");
assert_eq!(user.email, "test@example.com");
assert!(user.is_active);
assert!(!user.is_admin);
}
#[test]
fn test_username_validation() {
let result = User::new(
1,
"ab".to_string(), "test@example.com".to_string(),
"hashed_password".to_string(),
);
assert!(result.is_err());
}
#[test]
fn test_email_verification() {
let mut user = User::new(
1,
"testuser".to_string(),
"test@example.com".to_string(),
"hashed_password".to_string(),
).unwrap();
assert!(!user.email_verified);
user.verify_email();
assert!(user.email_verified);
}
#[test]
fn test_public_user_conversion() {
let user = User::new(
1,
"testuser".to_string(),
"test@example.com".to_string(),
"hashed_password".to_string(),
).unwrap();
let public = user.to_public();
assert_eq!(public.username, "testuser");
}
}