Implement GraphQL logic for SQLite
This commit is contained in:
		
							
								
								
									
										297
									
								
								src/graphql.rs
									
									
									
									
									
								
							
							
						
						
									
										297
									
								
								src/graphql.rs
									
									
									
									
									
								
							| @@ -1,4 +1,3 @@ | ||||
| use std::marker::PhantomData; | ||||
| use juniper::{ | ||||
|     EmptyMutation, | ||||
|     EmptySubscription, | ||||
| @@ -7,24 +6,102 @@ use juniper::{ | ||||
|     graphql_object, | ||||
|     GraphQLObject, | ||||
|     GraphQLEnum, | ||||
|     FieldResult, | ||||
|     FieldError, | ||||
| }; | ||||
| use sqlx::{ | ||||
|     SqlitePool, | ||||
|     types::chrono::{ | ||||
|         DateTime, | ||||
|         Utc, | ||||
|     }, | ||||
|     FromRow, | ||||
|     Encode, | ||||
|     Decode, | ||||
|     Type, | ||||
|     Row, | ||||
|     Result as SqlxResult, | ||||
|     Error as SqlxError, | ||||
| }; | ||||
| #[cfg(feature = "sqlite")] | ||||
| use sqlx::{ | ||||
|     SqlitePool, | ||||
|     sqlite::SqliteRow, | ||||
| }; | ||||
| use url::Url; | ||||
|  | ||||
| #[derive(Clone, Copy, Debug)] | ||||
| pub struct Context<'db> { | ||||
|     db: &'db SqlitePool, | ||||
| fn into_user_limit( | ||||
|     #[cfg(feature = "sqlite")] | ||||
|     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 { | ||||
|     id: ID, | ||||
|     user_name: String, | ||||
| @@ -35,7 +112,58 @@ pub struct User { | ||||
|     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 { | ||||
|     privacy_preferences: PrivacyPreferences, | ||||
|     notification_preferences: NotificationPreferences, | ||||
| @@ -43,6 +171,32 @@ pub struct UserPreferences { | ||||
|     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)] | ||||
| pub struct PrivacyPreferences { | ||||
|     discovery: RestrictionPolicy, | ||||
| @@ -57,49 +211,126 @@ pub struct PrivacyPreferences { | ||||
|     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 { | ||||
|     lock_details: bool, | ||||
|     do_not_disturb: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, GraphQLObject)] | ||||
| #[derive(Clone, Debug, GraphQLObject, Decode, Encode)] | ||||
| pub struct SecurityPreferences { | ||||
|     account_tokens: Vec<ID>, | ||||
|     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 { | ||||
|     privacy_preferences: PrivacyPreferences, | ||||
|     external_servers: RestrictionPolicy, | ||||
|     external_servers_limit: Option<Vec<Url>>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy, Debug, GraphQLEnum)] | ||||
| pub enum RestrictionPolicy { | ||||
|     Everyone, | ||||
|     Excluding, | ||||
|     Including, | ||||
|     None, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy, Debug)] | ||||
| pub struct Query<'db> { | ||||
|     phantom: PhantomData<&'db ()>, | ||||
| } | ||||
|  | ||||
| #[graphql_object(context = Context<'db>)] | ||||
| impl<'db> Query<'db> { | ||||
|     async fn users(context: &Context<'db>) -> Vec<User> { | ||||
|         unimplemented!() | ||||
| 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")?, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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 { | ||||
|     Everyone = 0, | ||||
|     Excluding = 1, | ||||
|     Including = 2, | ||||
|     None = 3, | ||||
| } | ||||
|  | ||||
| pub fn schema<'root_node, 'db>() -> Schema<'root_node, 'db> { | ||||
|     Schema::new(Query { | ||||
|         phantom: PhantomData, | ||||
|     }, EmptyMutation::new(), EmptySubscription::new()) | ||||
| #[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