From 74976dab57887a4d7e29b426cdf7422722fa58ee Mon Sep 17 00:00:00 2001 From: Santo Cariotti Date: Mon, 17 Oct 2022 22:08:09 +0200 Subject: Refactoring of mods --- src/user/models.rs | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 src/user/models.rs (limited to 'src/user/models.rs') diff --git a/src/user/models.rs b/src/user/models.rs new file mode 100644 index 0000000..2ee6a06 --- /dev/null +++ b/src/user/models.rs @@ -0,0 +1,250 @@ +use crate::{ + config::CONFIG, + db::get_client, + errors::AppError, + model::models::{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, + pub is_staff: Option, +} + +/// 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 + ORDER BY id DESC + 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.is_staff = payload.is_staff; + + self.validate() + .map_err(|error| AppError::BadRequest(error.to_string()))?; + + sqlx::query( + r#" + UPDATE users SET name = $1, username = $2, email = $3, is_staff = $4 WHERE id = $5 + "#, + ) + .bind(&payload.name) + .bind(&payload.username) + .bind(&payload.email) + .bind(payload.is_staff.unwrap_or_default()) + .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 + } +} -- cgit v1.2.3-71-g8e6c