Implement GraphQL logic for SQLite
This commit is contained in:
		
							
								
								
									
										299
									
								
								src/graphql.rs
									
									
									
									
									
								
							
							
						
						
									
										299
									
								
								src/graphql.rs
									
									
									
									
									
								
							| @@ -1,4 +1,3 @@ | |||||||
| use std::marker::PhantomData; |  | ||||||
| use juniper::{ | use juniper::{ | ||||||
|     EmptyMutation, |     EmptyMutation, | ||||||
|     EmptySubscription, |     EmptySubscription, | ||||||
| @@ -7,24 +6,102 @@ use juniper::{ | |||||||
|     graphql_object, |     graphql_object, | ||||||
|     GraphQLObject, |     GraphQLObject, | ||||||
|     GraphQLEnum, |     GraphQLEnum, | ||||||
|  |     FieldResult, | ||||||
|  |     FieldError, | ||||||
| }; | }; | ||||||
| use sqlx::{ | use sqlx::{ | ||||||
|     SqlitePool, |  | ||||||
|     types::chrono::{ |     types::chrono::{ | ||||||
|         DateTime, |         DateTime, | ||||||
|         Utc, |         Utc, | ||||||
|     }, |     }, | ||||||
|  |     FromRow, | ||||||
|  |     Encode, | ||||||
|  |     Decode, | ||||||
|  |     Type, | ||||||
|  |     Row, | ||||||
|  |     Result as SqlxResult, | ||||||
|  |     Error as SqlxError, | ||||||
|  | }; | ||||||
|  | #[cfg(feature = "sqlite")] | ||||||
|  | use sqlx::{ | ||||||
|  |     SqlitePool, | ||||||
|  |     sqlite::SqliteRow, | ||||||
| }; | }; | ||||||
| use url::Url; | use url::Url; | ||||||
|  |  | ||||||
| #[derive(Clone, Copy, Debug)] | fn into_user_limit( | ||||||
| pub struct Context<'db> { |     #[cfg(feature = "sqlite")] | ||||||
|     db: &'db SqlitePool, |     row: &SqliteRow, | ||||||
|  |     index: &str | ||||||
|  | ) -> SqlxResult<Option<Vec<String>>> { | ||||||
|  |     let row_value: Option<&str> = row.try_get(index)?; | ||||||
|  |     if let Some(s) = row_value { | ||||||
|  |         into_toml_iter(s, index).map(|i| Some(i.collect())) | ||||||
|  |     } else { | ||||||
|  |         Ok(None) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<'db> juniper::Context for Context<'db> {} | fn into_server_limit( | ||||||
|  |     #[cfg(feature = "sqlite")] | ||||||
|  |     row: &SqliteRow, | ||||||
|  |     index: &str | ||||||
|  | ) -> SqlxResult<Option<Vec<Url>>> { | ||||||
|  |     let row_value: Option<&str> = row.try_get(index)?; | ||||||
|  |     if let Some(s) = row_value { | ||||||
|  |         let mut url_vector = Vec::new(); | ||||||
|  |         for s in into_toml_iter(s, index)? { | ||||||
|  |             url_vector.push(Url::parse(s).map_err(into_column_decode_err(index))?); | ||||||
|  |         } | ||||||
|  |         Ok(Some(url_vector)) | ||||||
|  |     } else { | ||||||
|  |         Ok(None) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, GraphQLObject)] | #[inline] | ||||||
|  | fn into_column_decode_err<E>(index: &str) -> impl FnOnce(E) -> SqlxError + '_ | ||||||
|  | where | ||||||
|  |     E: Into<Box<dyn std::error::Error + Send + Sync>> | ||||||
|  | { | ||||||
|  |     move |err: E| SqlxError::ColumnDecode { | ||||||
|  |         index: index.to_owned(), | ||||||
|  |         source: err.into(), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[inline] | ||||||
|  | fn into_toml_iter<'d, T>(raw: &'d str, index: &str) -> SqlxResult<impl Iterator<Item = T>> | ||||||
|  | where | ||||||
|  |     T: serde::de::Deserialize<'d> | ||||||
|  | { | ||||||
|  |     Ok(toml::from_str::<Vec<T>>(raw) | ||||||
|  |         .map_err(into_column_decode_err(index))? | ||||||
|  |         .into_iter() | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[inline] | ||||||
|  | fn into_toml_iter_from_row<'d, T>( | ||||||
|  |     #[cfg(feature = "sqlite")] | ||||||
|  |     row: &'d SqliteRow, | ||||||
|  |     index: &str | ||||||
|  | ) -> SqlxResult<impl Iterator<Item = T>> | ||||||
|  | where | ||||||
|  |     T: serde::de::Deserialize<'d> | ||||||
|  | { | ||||||
|  |     into_toml_iter(row.try_get(index)?, index) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug)] | ||||||
|  | pub struct Context { | ||||||
|  |     #[cfg(feature = "sqlite")] | ||||||
|  |     db: SqlitePool, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl juniper::Context for Context {} | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug, Decode, Type)] | ||||||
| pub struct User { | pub struct User { | ||||||
|     id: ID, |     id: ID, | ||||||
|     user_name: String, |     user_name: String, | ||||||
| @@ -35,7 +112,58 @@ pub struct User { | |||||||
|     preferences: UserPreferences, |     preferences: UserPreferences, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, GraphQLObject)] | #[graphql_object(context = Context)] | ||||||
|  | impl User { | ||||||
|  |     pub const fn id(&self) -> &ID { | ||||||
|  |         &self.id | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub const fn user_name(&self) -> &String { | ||||||
|  |         &self.user_name | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub const fn display_name(&self) -> Option<&String> { | ||||||
|  |         self.display_name.as_ref() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub const fn activated(&self) -> bool { | ||||||
|  |         self.activated | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub const fn created(&self) -> &DateTime<Utc> { | ||||||
|  |         &self.created | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub const fn last_online(&self) -> Option<&DateTime<Utc>> { | ||||||
|  |         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 { | ||||||
|  |     async fn from_row_with_db( | ||||||
|  |         #[cfg(feature = "sqlite")] | ||||||
|  |         row: &SqliteRow, | ||||||
|  |         #[cfg(feature = "sqlite")] | ||||||
|  |         db: &SqlitePool, | ||||||
|  |     ) -> Result<Self, SqlxError> { | ||||||
|  |         let id = row.try_get::<String, _>("id")?.into(); | ||||||
|  |         Ok(User { | ||||||
|  |             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, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug, GraphQLObject, Decode, Encode, FromRow)] | ||||||
| pub struct UserPreferences { | pub struct UserPreferences { | ||||||
|     privacy_preferences: PrivacyPreferences, |     privacy_preferences: PrivacyPreferences, | ||||||
|     notification_preferences: NotificationPreferences, |     notification_preferences: NotificationPreferences, | ||||||
| @@ -43,6 +171,32 @@ 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, | ||||||
| @@ -57,49 +211,126 @@ pub struct PrivacyPreferences { | |||||||
|     info_server_limit: Option<Vec<Url>>, |     info_server_limit: Option<Vec<Url>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Copy, Debug, GraphQLObject)] | impl PrivacyPreferences { | ||||||
|  |     fn from_row_cfg( | ||||||
|  |         #[cfg(feature = "sqlite")] | ||||||
|  |         row: &SqliteRow, | ||||||
|  |     ) -> SqlxResult<Self> { | ||||||
|  |         Ok(PrivacyPreferences { | ||||||
|  |             discovery: row.try_get("discovery")?, | ||||||
|  |             discovery_user_limit: into_user_limit(row, "discovery_user_limit")?, | ||||||
|  |             discovery_server_limit: into_server_limit(row, "discovery_server_limit")?, | ||||||
|  |             last_seen: row.try_get("last_seen")?, | ||||||
|  |             last_seen_user_limit: into_user_limit(row, "last_seen_user_limit")?, | ||||||
|  |             last_seen_server_limit: into_server_limit(row, "last_seen_server_limit")?, | ||||||
|  |             last_seen_course: row.try_get("last_seen_course")?, | ||||||
|  |             info: row.try_get("info")?, | ||||||
|  |             info_user_limit: into_user_limit(row, "info_user_limit")?, | ||||||
|  |             info_server_limit: into_server_limit(row, "info_server_limit")?, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "sqlite")] | ||||||
|  | impl FromRow<'_, SqliteRow> for PrivacyPreferences { | ||||||
|  |     fn from_row(row: &SqliteRow) -> SqlxResult<Self> { | ||||||
|  |         Self::from_row_cfg(row) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, Debug, GraphQLObject, Decode, Encode, FromRow)] | ||||||
| pub struct NotificationPreferences { | pub struct NotificationPreferences { | ||||||
|     lock_details: bool, |     lock_details: bool, | ||||||
|     do_not_disturb: bool, |     do_not_disturb: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, GraphQLObject)] | #[derive(Clone, Debug, GraphQLObject, Decode, Encode)] | ||||||
| pub struct SecurityPreferences { | pub struct SecurityPreferences { | ||||||
|     account_tokens: Vec<ID>, |     account_tokens: Vec<ID>, | ||||||
|     password_hash: String, |     password_hash: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, GraphQLObject)] | impl SecurityPreferences { | ||||||
|  |     fn from_row_cfg( | ||||||
|  |         #[cfg(feature = "sqlite")] | ||||||
|  |         row: &SqliteRow, | ||||||
|  |     ) -> SqlxResult<Self> { | ||||||
|  |         Ok(SecurityPreferences { | ||||||
|  |             account_tokens: into_toml_iter_from_row::<&str>(row, "account_tokens")? | ||||||
|  |                 .map(ID::new) | ||||||
|  |                 .collect(), | ||||||
|  |             password_hash: row.try_get("password_hash")?, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "sqlite")] | ||||||
|  | impl FromRow<'_, SqliteRow> for SecurityPreferences  { | ||||||
|  |     fn from_row(row: &SqliteRow) -> SqlxResult<Self> { | ||||||
|  |         Self::from_row_cfg(row) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug, GraphQLObject, Decode, Encode, FromRow)] | ||||||
| pub struct ExternalServersPreferences { | pub struct ExternalServersPreferences { | ||||||
|     privacy_preferences: PrivacyPreferences, |     privacy_preferences: PrivacyPreferences, | ||||||
|     external_servers: RestrictionPolicy, |     external_servers: RestrictionPolicy, | ||||||
|     external_servers_limit: Option<Vec<Url>>, |     external_servers_limit: Option<Vec<Url>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Copy, Debug, GraphQLEnum)] | impl ExternalServersPreferences { | ||||||
| pub enum RestrictionPolicy { |     async fn from_row_with_db( | ||||||
|     Everyone, |         #[cfg(feature = "sqlite")] | ||||||
|     Excluding, |         row: SqliteRow, | ||||||
|     Including, |         #[cfg(feature = "sqlite")] | ||||||
|     None, |         db: &SqlitePool, | ||||||
| } |     ) -> SqlxResult<Self> { | ||||||
|  |         Ok(Self { | ||||||
| #[derive(Clone, Copy, Debug)] |             privacy_preferences: | ||||||
| pub struct Query<'db> { |                 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")?)) | ||||||
|     phantom: PhantomData<&'db ()>, |                 .fetch_one(db).await?, | ||||||
| } |             external_servers: row.try_get("external_servers")?, | ||||||
|  |             external_servers_limit: into_server_limit(&row, "external_server_limit")?, | ||||||
| #[graphql_object(context = Context<'db>)] |         }) | ||||||
| impl<'db> Query<'db> { |  | ||||||
|     async fn users(context: &Context<'db>) -> Vec<User> { |  | ||||||
|         unimplemented!() |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub type Schema<'root_node, 'db> = RootNode<'root_node, Query<'db>, EmptyMutation<Context<'db>>, EmptySubscription<Context<'db>>>; | #[derive(Clone, Copy, Debug, GraphQLEnum, Type)] | ||||||
|  | pub enum RestrictionPolicy { | ||||||
| pub fn schema<'root_node, 'db>() -> Schema<'root_node, 'db> { |     Everyone = 0, | ||||||
|     Schema::new(Query { |     Excluding = 1, | ||||||
|         phantom: PhantomData, |     Including = 2, | ||||||
|     }, EmptyMutation::new(), EmptySubscription::new()) |     None = 3, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, Debug)] | ||||||
|  | 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)] | ||||||
|  | impl Query { | ||||||
|  |     async fn users(context: &Context) -> FieldResult<Vec<User>> { | ||||||
|  |         Self::users_sqlx_result(context).await.map_err(FieldError::from) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async fn user_preferences(context: &Context, id: ID) -> FieldResult<UserPreferences> { | ||||||
|  |         UserPreferences::try_get(&id, &context.db).await.map_err(FieldError::from) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub type Schema<'root_node> = RootNode<'root_node, Query, EmptyMutation<Context>, EmptySubscription<Context>>; | ||||||
|  |  | ||||||
|  | pub fn schema<'root_node>() -> Schema<'root_node> { | ||||||
|  |     Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()) | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user