noise-server/src/graphql.rs

476 lines
16 KiB
Rust
Raw Normal View History

2021-07-21 10:23:14 +02:00
use juniper::{
EmptySubscription,
ID,
RootNode,
graphql_object,
GraphQLObject,
GraphQLInputObject,
2021-07-21 10:23:14 +02:00
GraphQLEnum,
2021-07-27 21:11:40 +02:00
FieldResult,
FieldError,
2021-07-21 10:23:14 +02:00
};
use sqlx::{
types::chrono::{
DateTime,
Utc,
},
2021-07-27 21:11:40 +02:00
FromRow,
Encode,
Decode,
Type,
Row,
Result as SqlxResult,
Error as SqlxError,
};
#[cfg(feature = "sqlite")]
use sqlx::{
SqlitePool,
sqlite::SqliteRow,
2021-07-21 10:23:14 +02:00
};
use url::Url;
use uuid::Uuid;
2021-07-21 10:23:14 +02:00
2021-07-27 21:11:40 +02:00
fn into_user_limit(
#[cfg(feature = "sqlite")]
row: &SqliteRow,
index: &str
) -> SqlxResult<Vec<String>> {
let row_value: &str = row.try_get(index)?;
into_toml_iter(row_value, index).map(|i| i.collect())
2021-07-27 21:11:40 +02:00
}
fn into_server_limit(
#[cfg(feature = "sqlite")]
row: &SqliteRow,
index: &str
) -> SqlxResult<Vec<Url>> {
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))?);
2021-07-27 21:11:40 +02:00
}
Ok(url_vector)
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
#[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(),
}
}
2021-07-21 10:23:14 +02:00
2021-07-27 21:11:40 +02:00
#[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)
}
fn id_to_uuid(id: &ID) -> Result<Uuid, uuid::Error> {
use std::str::FromStr;
Uuid::from_str(&id)
}
2021-07-27 21:11:40 +02:00
#[derive(Clone, Debug)]
pub struct Context {
#[cfg(feature = "sqlite")]
pub db: SqlitePool,
2021-07-27 21:11:40 +02:00
}
impl juniper::Context for Context {}
#[derive(Clone, Debug, Decode, Type)]
2021-07-21 10:23:14 +02:00
pub struct User {
// can we change this to Uuid?
pub id: ID,
pub user_name: String,
pub display_name: Option<String>,
pub activated: bool,
pub created: DateTime<Utc>,
pub last_online: Option<DateTime<Utc>>,
pub preferences: UserPreferences,
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
#[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, GraphQLInputObject)]
pub struct NewUser {
pub(crate) user_name: String,
pub(crate) display_name: Option<String>,
pub(crate) password_hash: String,
}
impl From<NewUser> for User {
fn from(new_user: NewUser) -> Self {
User {
// TODO: how do we generate uuids?
id: ID::new(Uuid::new_v4().to_string()),
user_name: new_user.user_name,
display_name: new_user.display_name,
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(),
},
}
}
}
2021-07-27 21:11:40 +02:00
#[derive(Clone, Debug, GraphQLObject, Decode, Encode, FromRow)]
2021-07-21 10:23:14 +02:00
pub struct UserPreferences {
privacy_preferences: PrivacyPreferences,
notification_preferences: NotificationPreferences,
security_preferences: SecurityPreferences,
external_servers_preferences: ExternalServersPreferences,
}
2021-07-27 21:11:40 +02:00
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,
})
}
}
2021-07-21 10:23:14 +02:00
#[derive(Clone, Debug, GraphQLObject)]
pub struct PrivacyPreferences {
discovery: RestrictionPolicy,
discovery_user_limit: Vec<String>,
discovery_server_limit: Vec<Url>,
2021-07-21 10:23:14 +02:00
last_seen: RestrictionPolicy,
last_seen_user_limit: Vec<String>,
last_seen_server_limit: Vec<Url>,
2021-07-21 10:23:14 +02:00
last_seen_course: bool,
info: RestrictionPolicy,
info_user_limit: Vec<String>,
info_server_limit: Vec<Url>,
}
impl Default for PrivacyPreferences {
fn default() -> Self {
PrivacyPreferences {
discovery: Default::default(),
discovery_user_limit: Default::default(),
discovery_server_limit: Default::default(),
last_seen: Default::default(),
last_seen_user_limit: Default::default(),
last_seen_server_limit: Default::default(),
last_seen_course: true,
info: Default::default(),
info_user_limit: Default::default(),
info_server_limit: Default::default(),
}
}
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
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, Default, GraphQLObject, Decode, Encode, FromRow)]
2021-07-21 10:23:14 +02:00
pub struct NotificationPreferences {
lock_details: bool,
do_not_disturb: bool,
}
2021-07-27 21:11:40 +02:00
#[derive(Clone, Debug, GraphQLObject, Decode, Encode)]
2021-07-21 10:23:14 +02:00
pub struct SecurityPreferences {
account_tokens: Vec<ID>,
password_hash: String,
}
2021-07-27 21:11:40 +02:00
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, Default, GraphQLObject, Decode, Encode, FromRow)]
2021-07-21 10:23:14 +02:00
pub struct ExternalServersPreferences {
privacy_preferences: PrivacyPreferences,
external_servers: RestrictionPolicy,
external_servers_limit: Vec<Url>,
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
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)]
2021-07-21 10:23:14 +02:00
pub enum RestrictionPolicy {
2021-07-27 21:11:40 +02:00
Everyone = 0,
Excluding = 1,
Including = 2,
None = 3,
2021-07-21 10:23:14 +02:00
}
impl Default for RestrictionPolicy {
fn default() -> Self {
Self::Everyone
}
}
2021-07-21 10:23:14 +02:00
#[derive(Clone, Copy, Debug)]
2021-07-27 21:11:40 +02:00
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)
}
2021-07-21 10:23:14 +02:00
}
2021-07-27 21:11:40 +02:00
#[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)
2021-07-21 10:23:14 +02:00
}
}
#[derive(Clone, Copy, Debug)]
pub struct Mutation;
#[graphql_object(context = Context)]
impl Mutation {
async fn createUser(context: &Context, new_user: NewUser) -> FieldResult<String> {
let user: User = new_user.into();
if sqlx::query(
format!(r#"SELECT id FROM users WHERE user_name="{}""#, user.user_name).as_str()
).fetch_all(&context.db).await?.len() > 0 {
return Err("username is already in use".into());
}
sqlx::query(format!(
r#"INSERT INTO users VALUES ("{}", "{}", {}, {}, {}, {})"#,
id_to_uuid(&user.id)?.to_simple(),
user.user_name,
user.display_name.map(|x| format!(r#""{}""#, x)).unwrap_or("NULL".to_string()),
user.activated as u8,
user.created.timestamp(),
match user.last_online {
None => "NULL".to_string(),
Some(d) => format!("{}", d.timestamp()),
},
).as_str()).execute(&context.db).await?;
let privacy_preferences = user.preferences.privacy_preferences;
sqlx::query(format!(
r#"INSERT INTO privacy_preferences VALUES ("{}", {}, "{}", "{}", {}, "{}", "{}", {}, {}, "{}", "{}")"#,
id_to_uuid(&user.id)?.to_simple(),
privacy_preferences.discovery as u8,
privacy_preferences.discovery_user_limit.join(","),
privacy_preferences.discovery_server_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","),
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::<Vec<_>>().join(","),
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::<Vec<_>>().join(","),
).as_str()).execute(&context.db).await?;
let notification_preferences = user.preferences.notification_preferences;
sqlx::query(format!(
r#"INSERT INTO notification_preferences VALUES ("{}", {}, {})"#,
id_to_uuid(&user.id)?.to_simple(),
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;
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::<Vec<_>>().join(","),
security_preferences.password_hash,
).as_str()).execute(&context.db).await?;
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(),
external_servers_preferences.external_servers as u8,
external_servers_preferences.external_servers_limit.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(","),
).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(),
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::<Vec<_>>().join(","),
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::<Vec<_>>().join(","),
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::<Vec<_>>().join(","),
).as_str()).execute(&context.db).await?;
Ok(format!("User @{} created", user.user_name))
}
}
pub type Schema<'root_node> = RootNode<'root_node, Query, Mutation, EmptySubscription<Context>>;
2021-07-21 10:23:14 +02:00
2021-07-27 21:11:40 +02:00
pub fn schema<'root_node>() -> Schema<'root_node> {
Schema::new(Query, Mutation, EmptySubscription::new())
2021-07-21 10:23:14 +02:00
}