diff --git a/Cargo.lock b/Cargo.lock index 74f40c4..b382922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,6 +580,12 @@ dependencies = [ "serde", ] +[[package]] +name = "email_address" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -3031,6 +3037,7 @@ version = "0.0.0" dependencies = [ "chrono", "database", + "email_address", "hash", "id", "thiserror", diff --git a/crates/database/queries/users/get_one_by_email.sql b/crates/database/queries/users/get_one_by_email.sql new file mode 100644 index 0000000..5fe7d03 --- /dev/null +++ b/crates/database/queries/users/get_one_by_email.sql @@ -0,0 +1,14 @@ +select id, + created_at as "created_at: DateTime", + updated_at as "updated_at: DateTime", + is_admin as "is_admin: bool", + username, + name, + email, + password, + password_recover, + paper_key, + is_archived as "is_archived: bool" +from users + +where email is (?) diff --git a/crates/database/queries/users/get_one_by_username.sql b/crates/database/queries/users/get_one_by_username.sql new file mode 100644 index 0000000..723288d --- /dev/null +++ b/crates/database/queries/users/get_one_by_username.sql @@ -0,0 +1,14 @@ +select id, + created_at as "created_at: DateTime", + updated_at as "updated_at: DateTime", + is_admin as "is_admin: bool", + username, + name, + email, + password, + password_recover, + paper_key, + is_archived as "is_archived: bool" +from users + +where username is (?) diff --git a/crates/database/sqlx-data.json b/crates/database/sqlx-data.json index 00bbbe7..e0fb04f 100644 --- a/crates/database/sqlx-data.json +++ b/crates/database/sqlx-data.json @@ -88,6 +88,84 @@ }, "query": "select id,\n created_at as \"created_at: DateTime\",\n updated_at as \"updated_at: DateTime\",\n business_name,\n business_logo,\n url\n\nfrom settings\n\nwhere id is 0\n" }, + "69752cc2a3fc91d4e7a39e0b167695f431380bd40df9638b5df3534715de04b0": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "created_at: DateTime", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "updated_at: DateTime", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "is_admin: bool", + "ordinal": 3, + "type_info": "Int64" + }, + { + "name": "username", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "email", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "password", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "password_recover", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "paper_key", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "is_archived: bool", + "ordinal": 10, + "type_info": "Int64" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "select id,\n created_at as \"created_at: DateTime\",\n updated_at as \"updated_at: DateTime\",\n is_admin as \"is_admin: bool\",\n username,\n name,\n email,\n password,\n password_recover,\n paper_key,\n is_archived as \"is_archived: bool\"\nfrom users\n\nwhere email is (?)\n" + }, "87906834faa6f185aee0e4d893b9754908b1c173e9dce383663d723891a89cd1": { "describe": { "columns": [], @@ -108,6 +186,84 @@ }, "query": "update settings\n\nset first_admin = ?\n\nwhere id is 0\n" }, + "b652e67e8e1cd0e2b55c830c5569eb1c6caf73857215b4298265cce5c5462902": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "created_at: DateTime", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "updated_at: DateTime", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "is_admin: bool", + "ordinal": 3, + "type_info": "Int64" + }, + { + "name": "username", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "email", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "password", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "password_recover", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "paper_key", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "is_archived: bool", + "ordinal": 10, + "type_info": "Int64" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "select id,\n created_at as \"created_at: DateTime\",\n updated_at as \"updated_at: DateTime\",\n is_admin as \"is_admin: bool\",\n username,\n name,\n email,\n password,\n password_recover,\n paper_key,\n is_archived as \"is_archived: bool\"\nfrom users\n\nwhere username is (?)\n" + }, "c5a57c971d07532ec0cc897b5ac06e0814e506f9c24647d1eaf44174dc0a5954": { "describe": { "columns": [ diff --git a/crates/database/src/tables/users.rs b/crates/database/src/tables/users.rs index e687e60..77a3dd9 100644 --- a/crates/database/src/tables/users.rs +++ b/crates/database/src/tables/users.rs @@ -51,4 +51,24 @@ impl Users { .await .map_err(handle_error) } + + pub async fn get_one_by_email( + conn: impl SqliteExecutor<'_>, + email: &str, + ) -> Result, Error> { + sqlx::query_file_as!(Self, "queries/users/get_one_by_email.sql", email) + .fetch_optional(conn) + .await + .map_err(handle_error) + } + + pub async fn get_one_by_username( + conn: impl SqliteExecutor<'_>, + username: &str, + ) -> Result, Error> { + sqlx::query_file_as!(Self, "queries/users/get_one_by_username.sql", username) + .fetch_optional(conn) + .await + .map_err(handle_error) + } } diff --git a/crates/ezidam/src/routes/root.rs b/crates/ezidam/src/routes/root.rs index 70a9f96..f45100d 100644 --- a/crates/ezidam/src/routes/root.rs +++ b/crates/ezidam/src/routes/root.rs @@ -47,7 +47,7 @@ async fn avatar( size: Option, ) -> Result { // Verify existence of user - let _user = User::get_by_id(&mut *db, &user_id.0) + let _user = User::get_by_login(&mut *db, user_id.0.as_ref()) .await? .ok_or_else(|| Error::not_found(user_id.0.to_string()))?; diff --git a/crates/users/Cargo.toml b/crates/users/Cargo.toml index ba87b9f..e9e7d31 100644 --- a/crates/users/Cargo.toml +++ b/crates/users/Cargo.toml @@ -8,4 +8,5 @@ database = { path = "../database" } hash = { path = "../hash" } id = { path = "../id" } thiserror = { workspace = true } -chrono = { workspace = true } \ No newline at end of file +chrono = { workspace = true } +email_address = { version = "0.2.4", default-features = false } diff --git a/crates/users/src/database.rs b/crates/users/src/database.rs index 77dd943..e25695c 100644 --- a/crates/users/src/database.rs +++ b/crates/users/src/database.rs @@ -2,8 +2,10 @@ use crate::error::Error; use crate::User; use database::sqlx::SqliteExecutor; use database::Users as DatabaseUsers; +use email_address::EmailAddress; use hash::Password; use id::UserID; +use std::str::FromStr; impl From for User { fn from(db: DatabaseUsers) -> Self { @@ -43,12 +45,46 @@ impl User { ) } - pub async fn get_by_id( - conn: impl SqliteExecutor<'_>, - id: &UserID, - ) -> Result, Error> { + async fn get_by_id(conn: impl SqliteExecutor<'_>, id: &UserID) -> Result, Error> { Ok(DatabaseUsers::get_one_by_id(conn, &id.0) .await? .map(Self::from)) } + + async fn get_by_email( + conn: impl SqliteExecutor<'_>, + email: &EmailAddress, + ) -> Result, Error> { + Ok(DatabaseUsers::get_one_by_email(conn, email.as_str()) + .await? + .map(Self::from)) + } + + async fn get_by_username( + conn: impl SqliteExecutor<'_>, + username: &str, + ) -> Result, Error> { + Ok(DatabaseUsers::get_one_by_username(conn, username) + .await? + .map(Self::from)) + } + + /// Get by ID, Email, Username (in that order) + pub async fn get_by_login( + conn: impl SqliteExecutor<'_>, + login: &str, + ) -> Result, Error> { + // Parse login as ID + if let Ok(id) = UserID::from_str(login) { + return Self::get_by_id(conn, &id).await; + } + + // Parse login as email + if let Ok(email) = EmailAddress::from_str(login) { + return Self::get_by_email(conn, &email).await; + } + + // Get user from username + Self::get_by_username(conn, login).await + } }