Implement GraphQL logic for SQLite
This commit is contained in:
parent
bc8d204a71
commit
40abea11f0
297
src/graphql.rs
297
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 {
|
||||||
|
Everyone = 0,
|
||||||
|
Excluding = 1,
|
||||||
|
Including = 2,
|
||||||
|
None = 3,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn schema<'root_node, 'db>() -> Schema<'root_node, 'db> {
|
#[derive(Clone, Copy, Debug)]
|
||||||
Schema::new(Query {
|
pub struct Query;
|
||||||
phantom: PhantomData,
|
|
||||||
}, EmptyMutation::new(), EmptySubscription::new())
|
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())
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user