From 54461e793482fb281913c90f2bb0118c922f56e0 Mon Sep 17 00:00:00 2001 From: EliasSchriefer Date: Sun, 17 Oct 2021 22:04:39 +0000 Subject: [PATCH] Fix connection between GraphQL and SQL At this point I don't even know where I started anymore. At least it works and the code looks kinda nice. This is something for future me. I need sleep. --- Cargo.lock | 1 + Cargo.toml | 2 +- src/graphql.rs | 270 ++++++++++++++++++++++++++----------------------- 3 files changed, 147 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cd097e..e2a206b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1993,6 +1993,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b3da181..9e147c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ structopt = { version = "^0.3", features = ["wrap_help"] } toml = "^0.5" serde = { version = "^1.0", features = ["derive"] } tokio = { version = "^1.0", features = ["rt-multi-thread", "macros", "sync"] } -url = "^2.2" +url = { version = "^2.2", features = ["serde"] } uuid = { version = "^0.8", features = ["v4", "v5"] } [features] diff --git a/src/graphql.rs b/src/graphql.rs index 1bab72b..c9e8e96 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -1,3 +1,11 @@ +use std::{ + convert::{ + TryFrom, + TryInto, + }, + fmt::Display, + str::FromStr, +}; use juniper::{ EmptySubscription, ID, @@ -35,21 +43,17 @@ fn into_user_limit( row: &SqliteRow, index: &str ) -> SqlxResult> { - let row_value: &str = row.try_get(index)?; - into_toml_iter(row_value, index).map(|i| i.collect()) + let row_value = row.try_get(index)?; + from_sql_formatted_array(row_value, index).map(|i| i.collect()) } +#[inline] fn into_server_limit( #[cfg(feature = "sqlite")] row: &SqliteRow, index: &str ) -> SqlxResult> { - let row_value: &str = row.try_get(index)?; - let mut url_vector = Vec::new(); - for s in into_toml_iter(row_value, index)? { - url_vector.push(Url::parse(s).map_err(into_column_decode_err(index))?); - } - Ok(url_vector) + Ok(from_sql_formatted_array(row.try_get(index)?, index)?.collect()) } #[inline] @@ -64,14 +68,38 @@ where } #[inline] -fn into_toml_iter<'d, T>(raw: &'d str, index: &str) -> SqlxResult> +fn from_sql_formatted_array(raw: String, index: &str) -> SqlxResult> where - T: serde::de::Deserialize<'d> + T: FromStr, + ::Err: Into>, { - Ok(toml::from_str::>(raw) - .map_err(into_column_decode_err(index))? - .into_iter() - ) + let mut raw_values: Vec<_> = raw.split(',').collect(); + if raw_values[0] == raw { + raw_values.clear(); + } + let mut values = Vec::new(); + for r in raw_values.drain(..) { + values.push(r.parse().map_err(into_column_decode_err(index))?); + } + Ok(values.into_iter()) +} + +#[inline] +fn format_array_for_sql(values: &[T]) -> String +where + T: Display +{ + match values.len() { + 1.. => { + let mut output = values[0].to_string(); + for v in &values[1..] { + output += ","; + output += &v.to_string(); + } + output + }, + _ => values.first().map(|v| v.to_string()).unwrap_or(String::new()), + } } #[inline] @@ -81,13 +109,14 @@ fn into_toml_iter_from_row<'d, T>( index: &str ) -> SqlxResult> where - T: serde::de::Deserialize<'d> + T: FromStr, + ::Err: Into>, { - into_toml_iter(row.try_get(index)?, index) + from_sql_formatted_array(row.try_get(index)?, index) } +#[inline] fn id_to_uuid(id: &ID) -> Result { - use std::str::FromStr; Uuid::from_str(&id) } @@ -108,7 +137,6 @@ pub struct User { pub activated: bool, pub created: DateTime, pub last_online: Option>, - pub preferences: UserPreferences, } #[graphql_object(context = Context)] @@ -136,28 +164,20 @@ impl User { pub const fn last_online(&self) -> Option<&DateTime> { self.last_online.as_ref() } - - pub async fn preferences(&self, context: &Context) -> FieldResult { - UserPreferences::try_get(&self.id, &context.db).await.map_err(FieldError::from) - } } -impl User { - async fn from_row_with_db( - #[cfg(feature = "sqlite")] - row: &SqliteRow, - #[cfg(feature = "sqlite")] - db: &SqlitePool, - ) -> Result { - let id = row.try_get::("id")?.into(); +impl TryFrom for User { + type Error = FieldError; + + fn try_from(row: SqliteRow) -> FieldResult { + let id = ID::new(row.try_get::("id")?); Ok(User { + id, user_name: row.try_get("user_name")?, display_name: row.try_get("display_name")?, activated: row.try_get("activated")?, created: row.try_get("created")?, last_online: row.try_get("last_online")?, - preferences: UserPreferences::try_get(&id, db).await?, - id, }) } } @@ -179,15 +199,20 @@ impl From for User { activated: false, created: Utc::now(), last_online: None, - preferences: UserPreferences { - privacy_preferences: Default::default(), - security_preferences: SecurityPreferences { - account_tokens: Vec::new(), - password_hash: new_user.password_hash, - }, - notification_preferences: Default::default(), - external_servers_preferences: Default::default(), + } + } +} + +impl From for UserPreferences { + fn from(new_user: NewUser) -> Self { + UserPreferences { + privacy_preferences: Default::default(), + security_preferences: SecurityPreferences { + account_tokens: Vec::new(), + password_hash: new_user.password_hash, }, + notification_preferences: Default::default(), + external_servers_preferences: Default::default(), } } } @@ -200,32 +225,6 @@ pub struct UserPreferences { external_servers_preferences: ExternalServersPreferences, } -impl UserPreferences { - async fn try_get(id: &ID, db: &SqlitePool) -> SqlxResult { - let privacy_preferences: PrivacyPreferences = - sqlx::query_as("SELECT * FROM privacy_preferences WHERE users.id = privacy_preferences.id") - .fetch_one(db).await?; - let notification_preferences: NotificationPreferences = - sqlx::query_as("SELECT * FROM notification_preferences WHERE users.id = privacy_preferences.id") - .fetch_one(db).await?; - let security_preferences: SecurityPreferences = - sqlx::query_as("SELECT * FROM security_preferences WHERE users.id = privacy_preferences.id") - .fetch_one(db).await?; - let external_servers_preferences = ExternalServersPreferences::from_row_with_db( - sqlx::query(&*format!("SELECT * FROM external_server_preferences WHERE id = {}", id)) - .fetch_one(db).await?, - db, - ).await?; - - Ok(UserPreferences { - privacy_preferences, - notification_preferences, - security_preferences, - external_servers_preferences, - }) - } -} - #[derive(Clone, Debug, GraphQLObject)] pub struct PrivacyPreferences { discovery: RestrictionPolicy, @@ -253,7 +252,6 @@ impl Default for PrivacyPreferences { info: Default::default(), info_user_limit: Default::default(), info_server_limit: Default::default(), - } } } @@ -303,7 +301,7 @@ impl SecurityPreferences { row: &SqliteRow, ) -> SqlxResult { Ok(SecurityPreferences { - account_tokens: into_toml_iter_from_row::<&str>(row, "account_tokens")? + account_tokens: into_toml_iter_from_row::(row, "account_tokens")? .map(ID::new) .collect(), password_hash: row.try_get("password_hash")?, @@ -325,24 +323,8 @@ pub struct ExternalServersPreferences { external_servers_limit: Vec, } -impl ExternalServersPreferences { - async fn from_row_with_db( - #[cfg(feature = "sqlite")] - row: SqliteRow, - #[cfg(feature = "sqlite")] - db: &SqlitePool, - ) -> SqlxResult { - Ok(Self { - privacy_preferences: - sqlx::query_as(&*format!("SELECT * FROM external_server_privacy_preferences WHERE external_servers_preferences.id = external_servers_privacy_preferences.id && id = {}", row.try_get::("id")?)) - .fetch_one(db).await?, - external_servers: row.try_get("external_servers")?, - external_servers_limit: into_server_limit(&row, "external_server_limit")?, - }) - } -} - #[derive(Clone, Copy, Debug, GraphQLEnum, Type)] +#[repr(u8)] pub enum RestrictionPolicy { Everyone = 0, Excluding = 1, @@ -359,18 +341,6 @@ impl Default for RestrictionPolicy { #[derive(Clone, Copy, Debug)] pub struct Query; -impl Query { - async fn users_sqlx_result(context: &Context) -> SqlxResult> { - let rows = sqlx::query("SELECT * FROM users") - .fetch_all(&context.db).await?; - let mut users = Vec::with_capacity(rows.capacity()); - for row in rows { - users.push(User::from_row_with_db(&row, &context.db).await?); - } - Ok(users) - } -} - #[graphql_object(context = Context)] impl Query { async fn getUserID(context: &Context, username: String) -> FieldResult { @@ -384,11 +354,59 @@ impl Query { } async fn users(context: &Context) -> FieldResult> { - Self::users_sqlx_result(context).await.map_err(FieldError::from) + let rows = sqlx::query("SELECT * FROM users") + .fetch_all(&context.db).await?; + + let mut users = Vec::with_capacity(rows.capacity()); + for row in rows { + users.push(row.try_into()?); + } + + Ok(users) } - async fn userPreferences(context: &Context, id: ID) -> FieldResult { - UserPreferences::try_get(&id, &context.db).await.map_err(FieldError::from) + async fn userPreferences(context: &Context, id: ID, password_hash: String) -> FieldResult { + let uuid = id_to_uuid(&id)?.to_simple(); + + let security_preferences: SecurityPreferences = sqlx::query_as(format!( + r#"SELECT * FROM security_preferences WHERE id = "{}""#, + uuid, + ).as_str()).fetch_one(&context.db).await?; + + if security_preferences.password_hash != password_hash { + return Err("incorrect password hash".into()) + } + + let privacy_preferences: PrivacyPreferences = sqlx::query_as(format!( + r#"SELECT * FROM privacy_preferences WHERE id = "{}""#, + uuid, + ).as_str()).fetch_one(&context.db).await?; + + let notification_preferences: NotificationPreferences = sqlx::query_as(format!( + r#"SELECT * FROM notification_preferences WHERE id = "{}""#, + uuid, + ).as_str()).fetch_one(&context.db).await?; + + let external_servers_preferences = sqlx::query(format!( + r#"SELECT * FROM external_servers_preferences WHERE id = "{}""#, + uuid, + ).as_str()).fetch_one(&context.db).await?; + + let external_servers_privacy_preferences: PrivacyPreferences = sqlx::query_as(format!( + r#"SELECT * FROM external_servers_privacy_preferences WHERE id = "{}""#, + uuid, + ).as_str()).fetch_one(&context.db).await?; + + Ok(UserPreferences { + privacy_preferences, + notification_preferences, + security_preferences, + external_servers_preferences: ExternalServersPreferences { + external_servers: external_servers_preferences.try_get("external_servers")?, + external_servers_limit: into_server_limit(&external_servers_preferences, "external_servers_limit")?, + privacy_preferences: external_servers_privacy_preferences, + }, + }) } } @@ -398,7 +416,9 @@ pub struct Mutation; #[graphql_object(context = Context)] impl Mutation { async fn createUser(context: &Context, new_user: NewUser) -> FieldResult { - let user: User = new_user.into(); + let user: User = new_user.clone().into(); + let uuid = id_to_uuid(&user.id)?.to_simple(); + let user_preferences: UserPreferences = new_user.into(); if sqlx::query( format!(r#"SELECT id FROM users WHERE user_name="{}""#, user.user_name).as_str() @@ -408,7 +428,7 @@ impl Mutation { sqlx::query(format!( r#"INSERT INTO users VALUES ("{}", "{}", {}, {}, {}, {})"#, - id_to_uuid(&user.id)?.to_simple(), + uuid, user.user_name, user.display_name.map(|x| format!(r#""{}""#, x)).unwrap_or("NULL".to_string()), user.activated as u8, @@ -419,60 +439,60 @@ impl Mutation { }, ).as_str()).execute(&context.db).await?; - let privacy_preferences = user.preferences.privacy_preferences; + let privacy_preferences = user_preferences.privacy_preferences; sqlx::query(format!( r#"INSERT INTO privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"#, - id_to_uuid(&user.id)?.to_simple(), + uuid, privacy_preferences.discovery as u8, - privacy_preferences.discovery_user_limit.join(","), - privacy_preferences.discovery_server_limit.iter().map(|u| u.to_string()).collect::>().join(","), + format_array_for_sql(&privacy_preferences.discovery_user_limit), + format_array_for_sql(&privacy_preferences.discovery_server_limit), privacy_preferences.last_seen as u8, - privacy_preferences.last_seen_user_limit.join(","), - privacy_preferences.last_seen_server_limit.iter().map(|u| u.to_string()).collect::>().join(","), + format_array_for_sql(&privacy_preferences.last_seen_user_limit), + format_array_for_sql(&privacy_preferences.last_seen_server_limit), privacy_preferences.last_seen_course as u8, privacy_preferences.info as u8, - privacy_preferences.info_user_limit.join(","), - privacy_preferences.info_server_limit.iter().map(|u| u.to_string()).collect::>().join(","), + format_array_for_sql(&privacy_preferences.info_user_limit), + format_array_for_sql(&privacy_preferences.info_server_limit), ).as_str()).execute(&context.db).await?; - let notification_preferences = user.preferences.notification_preferences; + let notification_preferences = user_preferences.notification_preferences; sqlx::query(format!( r#"INSERT INTO notification_preferences VALUES ("{}", {}, {})"#, - id_to_uuid(&user.id)?.to_simple(), + uuid, notification_preferences.lock_details as u8, notification_preferences.do_not_disturb as u8, ).as_str()).execute(&context.db).await?; - let security_preferences = user.preferences.security_preferences; + let security_preferences = user_preferences.security_preferences; sqlx::query(format!( r#"INSERT INTO security_preferences VALUES ("{}", "{}", "{}")"#, - id_to_uuid(&user.id)?.to_simple(), - security_preferences.account_tokens.iter().map(|id| id.as_ref()).collect::>().join(","), + uuid, + format_array_for_sql(&security_preferences.account_tokens), security_preferences.password_hash, ).as_str()).execute(&context.db).await?; - let external_servers_preferences = user.preferences.external_servers_preferences; + let external_servers_preferences = user_preferences.external_servers_preferences; sqlx::query(format!( r#"INSERT INTO external_servers_preferences VALUES ("{}", {}, "{}")"#, - id_to_uuid(&user.id)?.to_simple(), + uuid, external_servers_preferences.external_servers as u8, - external_servers_preferences.external_servers_limit.iter().map(|u| u.to_string()).collect::>().join(","), + format_array_for_sql(&external_servers_preferences.external_servers_limit), ).as_str()).execute(&context.db).await?; let external_servers_privacy_preferences = external_servers_preferences.privacy_preferences; sqlx::query(format!( r#"INSERT INTO external_servers_privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"#, - id_to_uuid(&user.id)?.to_simple(), + uuid, external_servers_privacy_preferences.discovery as u8, - external_servers_privacy_preferences.discovery_user_limit.join(","), - external_servers_privacy_preferences.discovery_server_limit.iter().map(|u| u.to_string()).collect::>().join(","), + format_array_for_sql(&external_servers_privacy_preferences.discovery_user_limit), + format_array_for_sql(&external_servers_privacy_preferences.discovery_server_limit), external_servers_privacy_preferences.last_seen as u8, - external_servers_privacy_preferences.last_seen_user_limit.join(","), - external_servers_privacy_preferences.last_seen_server_limit.iter().map(|u| u.to_string()).collect::>().join(","), + format_array_for_sql(&external_servers_privacy_preferences.last_seen_user_limit), + format_array_for_sql(&external_servers_privacy_preferences.last_seen_server_limit), external_servers_privacy_preferences.last_seen_course as u8, external_servers_privacy_preferences.info as u8, - external_servers_privacy_preferences.info_user_limit.join(","), - external_servers_privacy_preferences.info_server_limit.iter().map(|u| u.to_string()).collect::>().join(","), + format_array_for_sql(&external_servers_privacy_preferences.info_user_limit), + format_array_for_sql(&external_servers_privacy_preferences.info_server_limit), ).as_str()).execute(&context.db).await?; Ok(ID::new(id_to_uuid(&user.id)?.to_simple().to_string()))