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.
This commit is contained in:
Elias Schriefer 2021-10-17 22:04:39 +00:00
parent 15e9c596c2
commit 54461e7934
3 changed files with 147 additions and 126 deletions

1
Cargo.lock generated
View File

@ -1993,6 +1993,7 @@ dependencies = [
"idna", "idna",
"matches", "matches",
"percent-encoding", "percent-encoding",
"serde",
] ]
[[package]] [[package]]

View File

@ -16,7 +16,7 @@ structopt = { version = "^0.3", features = ["wrap_help"] }
toml = "^0.5" toml = "^0.5"
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
tokio = { version = "^1.0", features = ["rt-multi-thread", "macros", "sync"] } 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"] } uuid = { version = "^0.8", features = ["v4", "v5"] }
[features] [features]

View File

@ -1,3 +1,11 @@
use std::{
convert::{
TryFrom,
TryInto,
},
fmt::Display,
str::FromStr,
};
use juniper::{ use juniper::{
EmptySubscription, EmptySubscription,
ID, ID,
@ -35,21 +43,17 @@ fn into_user_limit(
row: &SqliteRow, row: &SqliteRow,
index: &str index: &str
) -> SqlxResult<Vec<String>> { ) -> SqlxResult<Vec<String>> {
let row_value: &str = row.try_get(index)?; let row_value = row.try_get(index)?;
into_toml_iter(row_value, index).map(|i| i.collect()) from_sql_formatted_array(row_value, index).map(|i| i.collect())
} }
#[inline]
fn into_server_limit( fn into_server_limit(
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
row: &SqliteRow, row: &SqliteRow,
index: &str index: &str
) -> SqlxResult<Vec<Url>> { ) -> SqlxResult<Vec<Url>> {
let row_value: &str = row.try_get(index)?; Ok(from_sql_formatted_array(row.try_get(index)?, index)?.collect())
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)
} }
#[inline] #[inline]
@ -64,14 +68,38 @@ where
} }
#[inline] #[inline]
fn into_toml_iter<'d, T>(raw: &'d str, index: &str) -> SqlxResult<impl Iterator<Item = T>> fn from_sql_formatted_array<T>(raw: String, index: &str) -> SqlxResult<impl Iterator<Item = T>>
where where
T: serde::de::Deserialize<'d> T: FromStr,
<T as FromStr>::Err: Into<Box<dyn std::error::Error + Send + Sync>>,
{ {
Ok(toml::from_str::<Vec<T>>(raw) let mut raw_values: Vec<_> = raw.split(',').collect();
.map_err(into_column_decode_err(index))? if raw_values[0] == raw {
.into_iter() 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<T>(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] #[inline]
@ -81,13 +109,14 @@ fn into_toml_iter_from_row<'d, T>(
index: &str index: &str
) -> SqlxResult<impl Iterator<Item = T>> ) -> SqlxResult<impl Iterator<Item = T>>
where where
T: serde::de::Deserialize<'d> T: FromStr,
<T as FromStr>::Err: Into<Box<dyn std::error::Error + Send + Sync>>,
{ {
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<Uuid, uuid::Error> { fn id_to_uuid(id: &ID) -> Result<Uuid, uuid::Error> {
use std::str::FromStr;
Uuid::from_str(&id) Uuid::from_str(&id)
} }
@ -108,7 +137,6 @@ pub struct User {
pub activated: bool, pub activated: bool,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
pub last_online: Option<DateTime<Utc>>, pub last_online: Option<DateTime<Utc>>,
pub preferences: UserPreferences,
} }
#[graphql_object(context = Context)] #[graphql_object(context = Context)]
@ -136,28 +164,20 @@ impl User {
pub const fn last_online(&self) -> Option<&DateTime<Utc>> { pub const fn last_online(&self) -> Option<&DateTime<Utc>> {
self.last_online.as_ref() self.last_online.as_ref()
} }
pub async fn preferences(&self, context: &Context) -> FieldResult<UserPreferences> {
UserPreferences::try_get(&self.id, &context.db).await.map_err(FieldError::from)
}
} }
impl User { impl TryFrom<SqliteRow> for User {
async fn from_row_with_db( type Error = FieldError;
#[cfg(feature = "sqlite")]
row: &SqliteRow, fn try_from(row: SqliteRow) -> FieldResult<Self> {
#[cfg(feature = "sqlite")] let id = ID::new(row.try_get::<String, _>("id")?);
db: &SqlitePool,
) -> Result<Self, SqlxError> {
let id = row.try_get::<String, _>("id")?.into();
Ok(User { Ok(User {
id,
user_name: row.try_get("user_name")?, user_name: row.try_get("user_name")?,
display_name: row.try_get("display_name")?, display_name: row.try_get("display_name")?,
activated: row.try_get("activated")?, activated: row.try_get("activated")?,
created: row.try_get("created")?, created: row.try_get("created")?,
last_online: row.try_get("last_online")?, last_online: row.try_get("last_online")?,
preferences: UserPreferences::try_get(&id, db).await?,
id,
}) })
} }
} }
@ -179,7 +199,13 @@ impl From<NewUser> for User {
activated: false, activated: false,
created: Utc::now(), created: Utc::now(),
last_online: None, last_online: None,
preferences: UserPreferences { }
}
}
impl From<NewUser> for UserPreferences {
fn from(new_user: NewUser) -> Self {
UserPreferences {
privacy_preferences: Default::default(), privacy_preferences: Default::default(),
security_preferences: SecurityPreferences { security_preferences: SecurityPreferences {
account_tokens: Vec::new(), account_tokens: Vec::new(),
@ -187,7 +213,6 @@ impl From<NewUser> for User {
}, },
notification_preferences: Default::default(), notification_preferences: Default::default(),
external_servers_preferences: Default::default(), external_servers_preferences: Default::default(),
},
} }
} }
} }
@ -200,32 +225,6 @@ pub struct UserPreferences {
external_servers_preferences: ExternalServersPreferences, external_servers_preferences: ExternalServersPreferences,
} }
impl UserPreferences {
async fn try_get(id: &ID, db: &SqlitePool) -> SqlxResult<Self> {
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)] #[derive(Clone, Debug, GraphQLObject)]
pub struct PrivacyPreferences { pub struct PrivacyPreferences {
discovery: RestrictionPolicy, discovery: RestrictionPolicy,
@ -253,7 +252,6 @@ impl Default for PrivacyPreferences {
info: Default::default(), info: Default::default(),
info_user_limit: Default::default(), info_user_limit: Default::default(),
info_server_limit: Default::default(), info_server_limit: Default::default(),
} }
} }
} }
@ -303,7 +301,7 @@ impl SecurityPreferences {
row: &SqliteRow, row: &SqliteRow,
) -> SqlxResult<Self> { ) -> SqlxResult<Self> {
Ok(SecurityPreferences { Ok(SecurityPreferences {
account_tokens: into_toml_iter_from_row::<&str>(row, "account_tokens")? account_tokens: into_toml_iter_from_row::<String>(row, "account_tokens")?
.map(ID::new) .map(ID::new)
.collect(), .collect(),
password_hash: row.try_get("password_hash")?, password_hash: row.try_get("password_hash")?,
@ -325,24 +323,8 @@ pub struct ExternalServersPreferences {
external_servers_limit: Vec<Url>, external_servers_limit: Vec<Url>,
} }
impl ExternalServersPreferences {
async fn from_row_with_db(
#[cfg(feature = "sqlite")]
row: SqliteRow,
#[cfg(feature = "sqlite")]
db: &SqlitePool,
) -> SqlxResult<Self> {
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::<String, _>("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)] #[derive(Clone, Copy, Debug, GraphQLEnum, Type)]
#[repr(u8)]
pub enum RestrictionPolicy { pub enum RestrictionPolicy {
Everyone = 0, Everyone = 0,
Excluding = 1, Excluding = 1,
@ -359,18 +341,6 @@ impl Default for RestrictionPolicy {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Query; pub struct Query;
impl Query {
async fn users_sqlx_result(context: &Context) -> SqlxResult<Vec<User>> {
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)] #[graphql_object(context = Context)]
impl Query { impl Query {
async fn getUserID(context: &Context, username: String) -> FieldResult<ID> { async fn getUserID(context: &Context, username: String) -> FieldResult<ID> {
@ -384,11 +354,59 @@ impl Query {
} }
async fn users(context: &Context) -> FieldResult<Vec<User>> { async fn users(context: &Context) -> FieldResult<Vec<User>> {
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()?);
} }
async fn userPreferences(context: &Context, id: ID) -> FieldResult<UserPreferences> { Ok(users)
UserPreferences::try_get(&id, &context.db).await.map_err(FieldError::from) }
async fn userPreferences(context: &Context, id: ID, password_hash: String) -> FieldResult<UserPreferences> {
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)] #[graphql_object(context = Context)]
impl Mutation { impl Mutation {
async fn createUser(context: &Context, new_user: NewUser) -> FieldResult<ID> { async fn createUser(context: &Context, new_user: NewUser) -> FieldResult<ID> {
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( if sqlx::query(
format!(r#"SELECT id FROM users WHERE user_name="{}""#, user.user_name).as_str() format!(r#"SELECT id FROM users WHERE user_name="{}""#, user.user_name).as_str()
@ -408,7 +428,7 @@ impl Mutation {
sqlx::query(format!( sqlx::query(format!(
r#"INSERT INTO users VALUES ("{}", "{}", {}, {}, {}, {})"#, r#"INSERT INTO users VALUES ("{}", "{}", {}, {}, {}, {})"#,
id_to_uuid(&user.id)?.to_simple(), uuid,
user.user_name, user.user_name,
user.display_name.map(|x| format!(r#""{}""#, x)).unwrap_or("NULL".to_string()), user.display_name.map(|x| format!(r#""{}""#, x)).unwrap_or("NULL".to_string()),
user.activated as u8, user.activated as u8,
@ -419,60 +439,60 @@ impl Mutation {
}, },
).as_str()).execute(&context.db).await?; ).as_str()).execute(&context.db).await?;
let privacy_preferences = user.preferences.privacy_preferences; let privacy_preferences = user_preferences.privacy_preferences;
sqlx::query(format!( sqlx::query(format!(
r#"INSERT INTO privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"#, r#"INSERT INTO privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"#,
id_to_uuid(&user.id)?.to_simple(), uuid,
privacy_preferences.discovery as u8, privacy_preferences.discovery as u8,
privacy_preferences.discovery_user_limit.join(","), format_array_for_sql(&privacy_preferences.discovery_user_limit),
privacy_preferences.discovery_server_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","), format_array_for_sql(&privacy_preferences.discovery_server_limit),
privacy_preferences.last_seen as u8, privacy_preferences.last_seen as u8,
privacy_preferences.last_seen_user_limit.join(","), format_array_for_sql(&privacy_preferences.last_seen_user_limit),
privacy_preferences.last_seen_server_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","), format_array_for_sql(&privacy_preferences.last_seen_server_limit),
privacy_preferences.last_seen_course as u8, privacy_preferences.last_seen_course as u8,
privacy_preferences.info as u8, privacy_preferences.info as u8,
privacy_preferences.info_user_limit.join(","), format_array_for_sql(&privacy_preferences.info_user_limit),
privacy_preferences.info_server_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","), format_array_for_sql(&privacy_preferences.info_server_limit),
).as_str()).execute(&context.db).await?; ).as_str()).execute(&context.db).await?;
let notification_preferences = user.preferences.notification_preferences; let notification_preferences = user_preferences.notification_preferences;
sqlx::query(format!( sqlx::query(format!(
r#"INSERT INTO notification_preferences VALUES ("{}", {}, {})"#, r#"INSERT INTO notification_preferences VALUES ("{}", {}, {})"#,
id_to_uuid(&user.id)?.to_simple(), uuid,
notification_preferences.lock_details as u8, notification_preferences.lock_details as u8,
notification_preferences.do_not_disturb as u8, notification_preferences.do_not_disturb as u8,
).as_str()).execute(&context.db).await?; ).as_str()).execute(&context.db).await?;
let security_preferences = user.preferences.security_preferences; let security_preferences = user_preferences.security_preferences;
sqlx::query(format!( sqlx::query(format!(
r#"INSERT INTO security_preferences VALUES ("{}", "{}", "{}")"#, r#"INSERT INTO security_preferences VALUES ("{}", "{}", "{}")"#,
id_to_uuid(&user.id)?.to_simple(), uuid,
security_preferences.account_tokens.iter().map(|id| id.as_ref()).collect::<Vec<_>>().join(","), format_array_for_sql(&security_preferences.account_tokens),
security_preferences.password_hash, security_preferences.password_hash,
).as_str()).execute(&context.db).await?; ).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!( sqlx::query(format!(
r#"INSERT INTO external_servers_preferences VALUES ("{}", {}, "{}")"#, 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 as u8,
external_servers_preferences.external_servers_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","), format_array_for_sql(&external_servers_preferences.external_servers_limit),
).as_str()).execute(&context.db).await?; ).as_str()).execute(&context.db).await?;
let external_servers_privacy_preferences = external_servers_preferences.privacy_preferences; let external_servers_privacy_preferences = external_servers_preferences.privacy_preferences;
sqlx::query(format!( sqlx::query(format!(
r#"INSERT INTO external_servers_privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"#, 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 as u8,
external_servers_privacy_preferences.discovery_user_limit.join(","), format_array_for_sql(&external_servers_privacy_preferences.discovery_user_limit),
external_servers_privacy_preferences.discovery_server_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","), 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 as u8,
external_servers_privacy_preferences.last_seen_user_limit.join(","), format_array_for_sql(&external_servers_privacy_preferences.last_seen_user_limit),
external_servers_privacy_preferences.last_seen_server_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","), 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.last_seen_course as u8,
external_servers_privacy_preferences.info as u8, external_servers_privacy_preferences.info as u8,
external_servers_privacy_preferences.info_user_limit.join(","), format_array_for_sql(&external_servers_privacy_preferences.info_user_limit),
external_servers_privacy_preferences.info_server_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","), format_array_for_sql(&external_servers_privacy_preferences.info_server_limit),
).as_str()).execute(&context.db).await?; ).as_str()).execute(&context.db).await?;
Ok(ID::new(id_to_uuid(&user.id)?.to_simple().to_string())) Ok(ID::new(id_to_uuid(&user.id)?.to_simple().to_string()))