use crate::{ config::CONFIG, db::get_client, errors::AppError, models::model::{Model, ModelUser}, }; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, NoneAsEmptyString}; use sqlx::Row; use validator::Validate; /// User model #[derive(Deserialize, Serialize, Validate)] pub struct User { id: i32, name: String, #[validate(length(min = 4, message = "Can not be empty"))] email: String, #[validate(length(min = 2, message = "Can not be empty"))] username: String, #[validate(length(min = 8, message = "Must be min 8 chars length"))] password: String, is_staff: Option, avatar: Option, } /// Paylod used for user editing #[derive(Deserialize)] pub struct UserEdit { pub name: String, pub email: String, pub username: String, } /// Response used to print a user (or a users list) #[serde_as] #[derive(Deserialize, Serialize, sqlx::FromRow, Validate)] pub struct UserList { pub id: i32, pub name: String, #[validate(length(min = 4, message = "Can not be empty"))] pub email: String, #[validate(length(min = 2, message = "Can not be empty"))] pub username: String, pub is_staff: Option, #[serde_as(as = "NoneAsEmptyString")] pub avatar: Option, } impl User { /// By default an user has id = 0. It is not created yet pub fn new(name: String, email: String, username: String, password: String) -> Self { Self { id: 0, name, email, username, password, is_staff: Some(false), avatar: None, } } /// Create a new user from the model using a SHA256 crypted password pub async fn create(user: User) -> Result { let pool = unsafe { get_client() }; user.validate() .map_err(|error| AppError::BadRequest(error.to_string()))?; let crypted_password = sha256::digest(user.password); let rec: UserList = sqlx::query_as( r#" INSERT INTO users (name, email, username, password) VALUES ( $1, $2, $3, $4) RETURNING id, name, email, username, is_staff, avatar "#, ) .bind(user.name) .bind(user.email) .bind(user.username) .bind(crypted_password) .fetch_one(pool) .await?; Ok(rec) } /// Find a user using the model. It used for login pub async fn find(user: User) -> Result { let pool = unsafe { get_client() }; let crypted_password = sha256::digest(user.password); let rec: UserList = sqlx::query_as( r#" SELECT id, name, email, username, is_staff, avatar FROM "users" WHERE username = $1 AND password = $2 "#, ) .bind(user.username) .bind(crypted_password) .fetch_one(pool) .await?; Ok(rec) } /// Returns the user with id = `user_id` pub async fn find_by_id(user_id: i32) -> Result { let pool = unsafe { get_client() }; let rec: UserList = sqlx::query_as( r#" SELECT id, name, email, username, is_staff, avatar FROM "users" WHERE id = $1 "#, ) .bind(user_id) .fetch_one(pool) .await?; Ok(rec) } /// List all users pub async fn list(page: i64) -> Result, AppError> { let pool = unsafe { get_client() }; let rows: Vec = sqlx::query_as( r#"SELECT id, name, email, username, is_staff, avatar FROM users LIMIT $1 OFFSET $2 "#, ) .bind(CONFIG.page_limit) .bind(CONFIG.page_limit * page) .fetch_all(pool) .await?; Ok(rows) } /// Return the number of users. pub async fn count() -> Result { let pool = unsafe { get_client() }; let cursor = sqlx::query(r#"SELECT COUNT(id) as count FROM users"#) .fetch_one(pool) .await?; let count: i64 = cursor.try_get(0).unwrap(); Ok(count) } /// Prevent the "uniquess" Postgres fields check. Check if username has been taken pub async fn username_has_taken(username: &String) -> Result { let pool = unsafe { get_client() }; let cursor = sqlx::query( r#" SELECT COUNT(id) as count FROM users WHERE username = $1 "#, ) .bind(username) .fetch_one(pool) .await?; let count: i64 = cursor.try_get(0).unwrap(); Ok(count > 0) } /// Prevent the "uniquess" Postgres fields check. Check if email has been taken pub async fn email_has_taken(email: &String) -> Result { let pool = unsafe { get_client() }; let cursor = sqlx::query( r#" SELECT COUNT(id) as count FROM users WHERE email = $1 "#, ) .bind(email) .fetch_one(pool) .await?; let count: i64 = cursor.try_get(0).unwrap(); Ok(count > 0) } } impl UserList { /// Edit an user avatar pub async fn edit_avatar(&mut self, avatar: Option) -> Result<(), AppError> { let pool = unsafe { get_client() }; sqlx::query( r#" UPDATE users SET avatar = $1 WHERE id = $2 "#, ) .bind(&avatar) .bind(self.id) .execute(pool) .await?; self.avatar = avatar; Ok(()) } /// Edit an user pub async fn edit(&mut self, payload: UserEdit) -> Result<(), AppError> { let pool = unsafe { get_client() }; // Make assignments before the `sqlx::query()` so to perform validation. // If the `AppError::BadRequest` is raised, the query (and then the update) will be skipped self.name = payload.name.clone(); self.username = payload.username.clone(); self.email = payload.email.clone(); self.validate() .map_err(|error| AppError::BadRequest(error.to_string()))?; sqlx::query( r#" UPDATE users SET name = $1, username = $2, email = $3 WHERE id = $4 "#, ) .bind(&payload.name) .bind(&payload.username) .bind(&payload.email) .bind(self.id) .execute(pool) .await?; Ok(()) } /// Get all models created by an user pub async fn get_models(&self, page: i64) -> Result, AppError> { Model::list_from_author(page, self.id).await } /// Returns the number of models for an user pub async fn count_models(&self) -> Result { Model::count_filter_by_author(self.id).await } }