From ddde58f80e61d41e1aa486c7ed1d4a03c164490a Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 7 Feb 2025 10:40:25 +0100 Subject: [PATCH 1/8] feat: [#801] added sorting to query in mysql driver --- src/databases/database.rs | 13 ++++++++++++- src/databases/mysql.rs | 26 +++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/databases/database.rs b/src/databases/database.rs index 83408db0..740d390c 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -73,6 +73,15 @@ pub enum Sorting { SizeDesc, } +/// Sorting options for users. +#[derive(Clone, Copy, Debug, Deserialize)] +pub enum UsersSorting { + DateRegisteredNewest, + DateRegisteredOldest, + UsernameAZ, + UsernameZA, +} + /// Database errors. #[derive(Debug)] pub enum Error { @@ -143,10 +152,12 @@ pub trait Database: Sync + Send { /// Get `UserProfile` from `username`. async fn get_user_profile_from_username(&self, username: &str) -> Result; - /// Get all user profiles in a paginated and sorted form as `UserProfilesResponse` from `search`,`offset` and `page_size`. + /// Get all user profiles in a paginated and sorted form as `UserProfilesResponse` from `search`, `filters`, `sort`, `offset` and `page_size`. async fn get_user_profiles_search_paginated( &self, search: &Option, + filters: &Option>, + sort: &UsersSorting, offset: u64, page_size: u8, ) -> Result; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index a4b93210..369099df 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -8,7 +8,7 @@ use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions}; use sqlx::{query, query_as, Acquire, ConnectOptions, MySqlPool}; use url::Url; -use super::database::TABLES_TO_TRUNCATE; +use super::database::{UsersSorting, TABLES_TO_TRUNCATE}; use crate::databases::database; use crate::databases::database::{Category, Database, Driver, Sorting, TorrentCompact}; use crate::models::category::CategoryId; @@ -158,6 +158,8 @@ impl Database for Mysql { async fn get_user_profiles_search_paginated( &self, search: &Option, + filters: &Option>, + sort: &UsersSorting, offset: u64, limit: u8, ) -> Result { @@ -166,7 +168,25 @@ impl Database for Mysql { Some(v) => format!("%{v}%"), }; - let mut query_string = "SELECT * FROM torrust_user_profiles WHERE username LIKE ?".to_string(); + let sort_query: String = match sort { + UsersSorting::DateRegisteredNewest => "date_registered ASC".to_string(), + UsersSorting::DateRegisteredOldest => "date_registered DESC".to_string(), + UsersSorting::UsernameAZ => "username ASC".to_string(), + UsersSorting::UsernameZA => "username DESC".to_string(), + }; + + let mut query_string = "SELECT + tp.user_id, + tp.username, + tp.email, + tp.email_verified, + tu.date_registered, + tu.administrator + FROM torrust_user_profiles tp + INNER JOIN torrust_users tu + ON tp.user_id = tu.user_id + WHERE username LIKE ?" + .to_string(); let count_query = format!("SELECT COUNT(*) as count FROM ({query_string}) AS count_table"); @@ -179,7 +199,7 @@ impl Database for Mysql { let count = count_result?; - query_string = format!("{query_string} LIMIT ?, ?"); + query_string = format!("{query_string} ORDER BY {sort_query} LIMIT ?, ?"); let res: Vec = sqlx::query_as::<_, UserProfile>(&query_string) .bind(user_name.clone()) From 07abaf1dda6ac6d9d5f6a50b50f27cc82892b3f7 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 13 Feb 2025 18:39:50 +0100 Subject: [PATCH 2/8] feat: [#801] added filtering for both drivers --- src/databases/database.rs | 11 +++++ src/databases/mysql.rs | 72 +++++++++++++++++++++++++++++--- src/databases/sqlite.rs | 88 +++++++++++++++++++++++++++++++++++++-- src/services/user.rs | 26 ++++++++++-- 4 files changed, 185 insertions(+), 12 deletions(-) diff --git a/src/databases/database.rs b/src/databases/database.rs index 740d390c..eeef29ba 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -82,6 +82,14 @@ pub enum UsersSorting { UsernameZA, } +/// Sorting options for users. +#[derive(Clone, Copy, Debug, Deserialize)] +pub enum UsersFilters { + EmailVerified, + EmailNotVerified, + TorrentUploader, +} + /// Database errors. #[derive(Debug)] pub enum Error { @@ -165,6 +173,9 @@ pub trait Database: Sync + Send { /// Get `UserCompact` from `user_id`. async fn get_user_compact_from_id(&self, user_id: i64) -> Result; + /// Get `UsersFilter` from `filter_name`. + async fn get_filters_from_name(&self, filter_name: &str) -> Option; + /// Get a user's `TrackerKey`. async fn get_user_tracker_key(&self, user_id: i64) -> Option; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 369099df..59bb5553 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -8,7 +8,7 @@ use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions}; use sqlx::{query, query_as, Acquire, ConnectOptions, MySqlPool}; use url::Url; -use super::database::{UsersSorting, TABLES_TO_TRUNCATE}; +use super::database::{UsersFilters, UsersSorting, TABLES_TO_TRUNCATE}; use crate::databases::database; use crate::databases::database::{Category, Database, Driver, Sorting, TorrentCompact}; use crate::models::category::CategoryId; @@ -175,7 +175,57 @@ impl Database for Mysql { UsersSorting::UsernameZA => "username DESC".to_string(), }; - let mut query_string = "SELECT + let join_filters_query = if let Some(filters) = filters { + let mut join_filters = String::new(); + for filter in filters { + // don't take user input in the db query to smt join filter query + if let Some(sanitized_filter) = self.get_filters_from_name(filter).await { + match sanitized_filter { + UsersFilters::TorrentUploader => join_filters.push_str( + "INNER JOIN torrust_torrents tt + ON tu.user_id = tt_uploader_id", + ), + _ => break, + } + } + } + join_filters + } else { + String::new() + }; + + let where_filters_query = if let Some(filters) = filters { + let mut i = 0; + let mut where_filters = String::new(); + for filter in filters { + // don't take user input in the db query + if let Some(sanitized_filter) = self.get_filters_from_name(filter).await { + let mut filter_query = String::new(); + match sanitized_filter { + UsersFilters::EmailNotVerified => filter_query.push_str("email_verified = false"), + UsersFilters::EmailVerified => filter_query.push_str("email_verified = true"), + _ => break, + }; + + let mut str = format!("'{}'", filter_query); + if i > 0 { + str = format!(" AND {str}"); + } + where_filters.push_str(&str); + i += 1; + } + } + if where_filters.is_empty() { + String::new() + } else { + String::new() + } + } else { + String::new() + }; + + let mut query_string = format!( + "SELECT tp.user_id, tp.username, tp.email, @@ -183,10 +233,13 @@ impl Database for Mysql { tu.date_registered, tu.administrator FROM torrust_user_profiles tp - INNER JOIN torrust_users tu + INNER JOIN torrust_users tu ON tp.user_id = tu.user_id - WHERE username LIKE ?" - .to_string(); + {join_filters_query} + WHERE username LIKE ? + {where_filters_query} + " + ); let count_query = format!("SELECT COUNT(*) as count FROM ({query_string}) AS count_table"); @@ -223,6 +276,15 @@ impl Database for Mysql { .map_err(|_| database::Error::UserNotFound) } + async fn get_filters_from_name(&self, filter_name: &str) -> Option { + match filter_name { + "torrentUploader" => Some(UsersFilters::TorrentUploader), + "emailNotVerified" => Some(UsersFilters::EmailNotVerified), + "emailVerified" => Some(UsersFilters::EmailVerified), + _ => None, + } + } + /// Gets User Tracker Key /// /// # Panics diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index a3e77774..cdfc3f33 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -8,7 +8,7 @@ use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::{query, query_as, Acquire, ConnectOptions, SqlitePool}; use url::Url; -use super::database::TABLES_TO_TRUNCATE; +use super::database::{UsersFilters, UsersSorting, TABLES_TO_TRUNCATE}; use crate::databases::database; use crate::databases::database::{Category, Database, Driver, Sorting, TorrentCompact}; use crate::models::category::CategoryId; @@ -159,6 +159,8 @@ impl Database for Sqlite { async fn get_user_profiles_search_paginated( &self, search: &Option, + filters: &Option>, + sort: &UsersSorting, offset: u64, limit: u8, ) -> Result { @@ -167,7 +169,78 @@ impl Database for Sqlite { Some(v) => format!("%{v}%"), }; - let mut query_string = "SELECT * FROM torrust_user_profiles WHERE username LIKE ?".to_string(); + let sort_query: String = match sort { + UsersSorting::DateRegisteredNewest => "date_registered ASC".to_string(), + UsersSorting::DateRegisteredOldest => "date_registered DESC".to_string(), + UsersSorting::UsernameAZ => "username ASC".to_string(), + UsersSorting::UsernameZA => "username DESC".to_string(), + }; + + let join_filters_query = if let Some(filters) = filters { + let mut join_filters = String::new(); + for filter in filters { + // don't take user input in the db query + if let Some(sanitized_filter) = self.get_filters_from_name(filter).await { + match sanitized_filter { + UsersFilters::TorrentUploader => join_filters.push_str( + "INNER JOIN torrust_torrents tt + ON tu.user_id = tt_uploader_id", + ), + _ => break, + } + } + } + join_filters + } else { + String::new() + }; + + let where_filters_query = if let Some(filters) = filters { + let mut i = 0; + let mut where_filters = String::new(); + for filter in filters { + // don't take user input in the db query + if let Some(sanitized_filter) = self.get_filters_from_name(filter).await { + let mut filter_query = String::new(); + match sanitized_filter { + UsersFilters::EmailNotVerified => filter_query.push_str("email_verified = false"), + UsersFilters::EmailVerified => filter_query.push_str("email_verified = true"), + _ => break, + }; + + let mut str = format!("'{}'", filter_query); + if i > 0 { + str = format!(" AND {str}"); + } + where_filters.push_str(&str); + i += 1; + } + } + if where_filters.is_empty() { + String::new() + } else { + String::new() + } + } else { + String::new() + }; + + let mut query_string = format!( + "SELECT + tp.user_id, + tp.username, + tp.email, + tp.email_verified, + tu.date_registered, + tu.administrator + FROM torrust_user_profiles tp + INNER JOIN torrust_users tu + ON tp.user_id = tu.user_id + {join_filters_query} + WHERE username LIKE ? + {where_filters_query} + " + ); let count_query = format!("SELECT COUNT(*) as count FROM ({query_string}) AS count_table"); @@ -180,7 +253,7 @@ impl Database for Sqlite { let count = count_result?; - query_string = format!("{query_string} LIMIT ?, ?"); + query_string = format!("{query_string} ORDER BY {sort_query} LIMIT ?, ?"); let res: Vec = sqlx::query_as::<_, UserProfile>(&query_string) .bind(user_name.clone()) @@ -204,6 +277,15 @@ impl Database for Sqlite { .map_err(|_| database::Error::UserNotFound) } + async fn get_filters_from_name(&self, filter_name: &str) -> Option { + match filter_name { + "torrentUploader" => Some(UsersFilters::TorrentUploader), + "emailNotVerified" => Some(UsersFilters::EmailNotVerified), + "emailVerified" => Some(UsersFilters::EmailVerified), + _ => None, + } + } + async fn get_user_tracker_key(&self, user_id: i64) -> Option { const HOUR_IN_SECONDS: i64 = 3600; diff --git a/src/services/user.rs b/src/services/user.rs index c55443b1..d9e08186 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -14,15 +14,15 @@ use tracing::{debug, info}; use super::authentication::DbUserAuthenticationRepository; use super::authorization::{self, ACTION}; use crate::config::{Configuration, PasswordConstraints}; -use crate::databases::database::{Database, Error}; +use crate::databases::database::{Database, Error, UsersSorting}; use crate::errors::ServiceError; -use crate::mailer; use crate::mailer::VerifyClaims; use crate::models::response::UserProfilesResponse; use crate::models::user::{UserCompact, UserId, UserProfile, Username}; use crate::services::authentication::verify_password; use crate::utils::validation::validate_email_address; use crate::web::api::server::v1::contexts::user::forms::{ChangePasswordForm, RegistrationForm}; +use crate::{mailer, AsCSV}; /// Since user email could be optional, we need a way to represent "no email" /// in the database. This function returns the string that should be used for @@ -34,6 +34,9 @@ fn no_email() -> String { /// User request to generate a user profile listing. #[derive(Debug, Deserialize)] pub struct ListingRequest { + /// Expects comma separated string + pub filters: Option, + pub sort: Option, pub page_size: Option, pub page: Option, pub search: Option, @@ -43,6 +46,9 @@ pub struct ListingRequest { #[derive(Debug, Deserialize)] pub struct ListingSpecification { pub offset: u64, + /// Expects comma separated string + pub filters: Option>, + pub sort: UsersSorting, pub page_size: u8, pub search: Option, } @@ -403,10 +409,16 @@ impl ListingService { let offset = u64::from(page * u32::from(page_size)); + let sort = request.sort.unwrap_or(UsersSorting::UsernameAZ); + + let filters = request.filters.as_csv::().unwrap_or(None); + ListingSpecification { - search: request.search.clone(), offset, + filters, + sort, page_size, + search: request.search.clone(), } } } @@ -510,7 +522,13 @@ impl DbUserProfileRepository { /// It returns an error if there is a database error. pub async fn generate_listing(&self, specification: &ListingSpecification) -> Result { self.database - .get_user_profiles_search_paginated(&specification.search, specification.offset, specification.page_size) + .get_user_profiles_search_paginated( + &specification.search, + &specification.filters, + &specification.sort, + specification.offset, + specification.page_size, + ) .await } } From 357a21fe828caee8a38cd6ae631c8431bc2aea31 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 17 Feb 2025 18:14:34 +0100 Subject: [PATCH 3/8] feat: [#801] new user listing type --- src/models/response.rs | 4 ++-- src/models/user.rs | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/models/response.rs b/src/models/response.rs index 8f2806b1..33d6498b 100644 --- a/src/models/response.rs +++ b/src/models/response.rs @@ -3,7 +3,7 @@ use url::Url; use super::category::Category; use super::torrent::TorrentId; -use super::user::UserProfile; +use super::user::UserListing; use crate::databases::database::Category as DatabaseCategory; use crate::models::torrent::TorrentListing; use crate::models::torrent_file::TorrentFile; @@ -129,5 +129,5 @@ pub struct TorrentsResponse { #[derive(Serialize, Deserialize, Debug, sqlx::FromRow)] pub struct UserProfilesResponse { pub total: u32, - pub results: Vec, + pub results: Vec, } diff --git a/src/models/user.rs b/src/models/user.rs index 8347357c..b4c26d0e 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -55,6 +55,17 @@ pub struct UserFull { pub avatar: String, } +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, sqlx::FromRow)] +pub struct UserListing { + pub user_id: UserId, + pub username: String, + pub email: String, + pub email_verified: bool, + pub date_registered: String, + pub administrator: bool, +} + #[allow(clippy::module_name_repetitions)] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct UserClaims { From d72781191c925f8ca7f524fff8feb78c43a160ea Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 17 Feb 2025 18:15:54 +0100 Subject: [PATCH 4/8] refactor: [#801] fixed filtering and sorting not working --- src/databases/mysql.rs | 14 +++++--------- src/databases/sqlite.rs | 19 +++++++++---------- src/services/user.rs | 12 ++++++++++-- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 59bb5553..14abf165 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -19,7 +19,7 @@ use crate::models::torrent_file::{ }; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; -use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; +use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserListing, UserProfile}; use crate::services::torrent::{CanonicalInfoHashGroup, DbTorrentInfoHash}; use crate::utils::clock::{self, datetime_now, DATETIME_FORMAT}; use crate::utils::hex::from_bytes; @@ -204,10 +204,10 @@ impl Database for Mysql { match sanitized_filter { UsersFilters::EmailNotVerified => filter_query.push_str("email_verified = false"), UsersFilters::EmailVerified => filter_query.push_str("email_verified = true"), - _ => break, + _ => continue, }; - let mut str = format!("'{}'", filter_query); + let mut str = format!("AND {}", filter_query); if i > 0 { str = format!(" AND {str}"); } @@ -215,11 +215,7 @@ impl Database for Mysql { i += 1; } } - if where_filters.is_empty() { - String::new() - } else { - String::new() - } + where_filters } else { String::new() }; @@ -254,7 +250,7 @@ impl Database for Mysql { query_string = format!("{query_string} ORDER BY {sort_query} LIMIT ?, ?"); - let res: Vec = sqlx::query_as::<_, UserProfile>(&query_string) + let res: Vec = sqlx::query_as::<_, UserListing>(&query_string) .bind(user_name.clone()) .bind(i64::saturating_add_unsigned(0, offset)) .bind(limit) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index cdfc3f33..20779975 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -19,7 +19,7 @@ use crate::models::torrent_file::{ }; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; -use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; +use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserListing, UserProfile}; use crate::services::torrent::{CanonicalInfoHashGroup, DbTorrentInfoHash}; use crate::utils::clock::{self, datetime_now, DATETIME_FORMAT}; use crate::utils::hex::from_bytes; @@ -205,10 +205,10 @@ impl Database for Sqlite { match sanitized_filter { UsersFilters::EmailNotVerified => filter_query.push_str("email_verified = false"), UsersFilters::EmailVerified => filter_query.push_str("email_verified = true"), - _ => break, + _ => continue, }; - let mut str = format!("'{}'", filter_query); + let mut str = format!("AND {}", filter_query); if i > 0 { str = format!(" AND {str}"); } @@ -216,11 +216,7 @@ impl Database for Sqlite { i += 1; } } - if where_filters.is_empty() { - String::new() - } else { - String::new() - } + where_filters } else { String::new() }; @@ -255,13 +251,16 @@ impl Database for Sqlite { query_string = format!("{query_string} ORDER BY {sort_query} LIMIT ?, ?"); - let res: Vec = sqlx::query_as::<_, UserProfile>(&query_string) + let res: Vec = sqlx::query_as::<_, UserListing>(&query_string) .bind(user_name.clone()) .bind(i64::saturating_add_unsigned(0, offset)) .bind(limit) .fetch_all(&self.pool) .await - .map_err(|_| database::Error::Error)?; + .map_err(|e| { + eprintln!("Database error: {:?}", e); + database::Error::Error + })?; Ok(UserProfilesResponse { total: u32::try_from(count).expect("variable `count` is larger than u32"), diff --git a/src/services/user.rs b/src/services/user.rs index d9e08186..4c2a908f 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -36,7 +36,7 @@ fn no_email() -> String { pub struct ListingRequest { /// Expects comma separated string pub filters: Option, - pub sort: Option, + pub sort: Option, pub page_size: Option, pub page: Option, pub search: Option, @@ -409,7 +409,15 @@ impl ListingService { let offset = u64::from(page * u32::from(page_size)); - let sort = request.sort.unwrap_or(UsersSorting::UsernameAZ); + let sort = request.sort.clone().unwrap_or("UsernameAZ".to_string()); + + let sort = match sort.as_str() { + "dateRegisteredASC" => UsersSorting::DateRegisteredNewest, + "dateRegisteredDESC" => UsersSorting::DateRegisteredOldest, + "usernameASC" => UsersSorting::UsernameAZ, + "usernameDESC" => UsersSorting::UsernameZA, + _ => UsersSorting::UsernameAZ, + }; let filters = request.filters.as_csv::().unwrap_or(None); From d1303913c2a393aed9e7cc8f2793cc3fb5a0a252 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 18 Feb 2025 16:05:49 +0100 Subject: [PATCH 5/8] refactor: [#801] fixed clippy warnings --- src/databases/mysql.rs | 2 +- src/databases/sqlite.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 14abf165..cb4de978 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -207,7 +207,7 @@ impl Database for Mysql { _ => continue, }; - let mut str = format!("AND {}", filter_query); + let mut str = format!("AND {filter_query}"); if i > 0 { str = format!(" AND {str}"); } diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 20779975..a0b192f4 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -208,7 +208,7 @@ impl Database for Sqlite { _ => continue, }; - let mut str = format!("AND {}", filter_query); + let mut str = format!("AND {filter_query}"); if i > 0 { str = format!(" AND {str}"); } @@ -258,7 +258,7 @@ impl Database for Sqlite { .fetch_all(&self.pool) .await .map_err(|e| { - eprintln!("Database error: {:?}", e); + eprintln!("Database error: {e:?}"); database::Error::Error })?; From ffba3595ecce39e597696051e6002a809cb58a80 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 21 Feb 2025 12:10:51 +0100 Subject: [PATCH 6/8] refactor: [#801] restore error handling to previous version --- src/databases/sqlite.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index a0b192f4..2e19f73c 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -257,10 +257,7 @@ impl Database for Sqlite { .bind(limit) .fetch_all(&self.pool) .await - .map_err(|e| { - eprintln!("Database error: {e:?}"); - database::Error::Error - })?; + .map_err(|_| database::Error::Error)?; Ok(UserProfilesResponse { total: u32::try_from(count).expect("variable `count` is larger than u32"), From c0a675e69bf03a8d166d4bdb1fca13fe89047100 Mon Sep 17 00:00:00 2001 From: Mario Date: Sat, 22 Feb 2025 20:50:11 +0100 Subject: [PATCH 7/8] refactor: [#801] fixed 500 error when using two where filters at the same time and code cleanup --- src/databases/mysql.rs | 10 +++------- src/databases/sqlite.rs | 10 +++------- src/services/user.rs | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index cb4de978..2ec1d336 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -183,7 +183,7 @@ impl Database for Mysql { match sanitized_filter { UsersFilters::TorrentUploader => join_filters.push_str( "INNER JOIN torrust_torrents tt - ON tu.user_id = tt_uploader_id", + ON tu.user_id = tt.uploader_id", ), _ => break, } @@ -195,7 +195,6 @@ impl Database for Mysql { }; let where_filters_query = if let Some(filters) = filters { - let mut i = 0; let mut where_filters = String::new(); for filter in filters { // don't take user input in the db query @@ -207,12 +206,9 @@ impl Database for Mysql { _ => continue, }; - let mut str = format!("AND {filter_query}"); - if i > 0 { - str = format!(" AND {str}"); - } + let str = format!("AND {filter_query} "); + where_filters.push_str(&str); - i += 1; } } where_filters diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 2e19f73c..316d22ee 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -184,7 +184,7 @@ impl Database for Sqlite { match sanitized_filter { UsersFilters::TorrentUploader => join_filters.push_str( "INNER JOIN torrust_torrents tt - ON tu.user_id = tt_uploader_id", + ON tu.user_id = tt.uploader_id ", ), _ => break, } @@ -196,7 +196,6 @@ impl Database for Sqlite { }; let where_filters_query = if let Some(filters) = filters { - let mut i = 0; let mut where_filters = String::new(); for filter in filters { // don't take user input in the db query @@ -208,12 +207,9 @@ impl Database for Sqlite { _ => continue, }; - let mut str = format!("AND {filter_query}"); - if i > 0 { - str = format!(" AND {str}"); - } + let str = format!("AND {filter_query} "); + where_filters.push_str(&str); - i += 1; } } where_filters diff --git a/src/services/user.rs b/src/services/user.rs index 4c2a908f..e9a63d36 100644 --- a/src/services/user.rs +++ b/src/services/user.rs @@ -409,7 +409,7 @@ impl ListingService { let offset = u64::from(page * u32::from(page_size)); - let sort = request.sort.clone().unwrap_or("UsernameAZ".to_string()); + let sort = request.sort.clone().unwrap_or("usernameASC".to_string()); let sort = match sort.as_str() { "dateRegisteredASC" => UsersSorting::DateRegisteredNewest, From e462f945c7160d2f6dfd6dc1644c2844b45fe8b2 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 27 Feb 2025 00:31:37 +0100 Subject: [PATCH 8/8] feat: [#801] new service error for invalid user listing --- src/errors.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/errors.rs b/src/errors.rs index 36dd861c..4c26dc1b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -175,6 +175,8 @@ pub enum ServiceError { #[display("Invalid tracker API token.")] InvalidTrackerToken, // End tracker errors + #[display("Invalid user listing fields in the URL params.")] + InvalidUserListing, } impl From for ServiceError { @@ -326,6 +328,7 @@ pub fn http_status_code_for_service_error(error: &ServiceError) -> StatusCode { ServiceError::TorrentNotFoundInTracker => StatusCode::NOT_FOUND, ServiceError::InvalidTrackerToken => StatusCode::INTERNAL_SERVER_ERROR, ServiceError::LoggedInUserNotFound => StatusCode::UNAUTHORIZED, + ServiceError::InvalidUserListing => StatusCode::BAD_REQUEST, } }