From 425e00acc1681a5fa6b46a38dc37c368497c9fd4 Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Mon, 16 Jan 2023 00:09:50 +0100 Subject: [PATCH] Manually add users to database, better handling of errors in API Also manually remove users from database, and list them only as admin --- src/db/mod.rs | 122 ++++++++++++++++++++++++++++++++++++---- src/db/models/users.rs | 4 +- src/graphql/mutation.rs | 44 +++++++++++++-- src/graphql/query.rs | 14 ++++- 4 files changed, 167 insertions(+), 17 deletions(-) diff --git a/src/db/mod.rs b/src/db/mod.rs index 6b9a24f..490af96 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -4,12 +4,53 @@ pub mod schema; use self::models::languages::Language; use self::models::users::User; use self::models::words::Word; + use diesel::pg::PgConnection; use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; +use diesel::{insert_into, prelude::*}; + use dotenvy::dotenv; +use juniper::{graphql_value, DefaultScalarValue, FieldError, IntoFieldError}; use std::env; +use std::error::Error; use tracing::info; +#[derive(Debug)] +pub struct DatabaseError { + long: String, + short: String, +} + +impl DatabaseError { + pub fn new(long: S, short: T) -> Self + where + T: ToString, + S: ToString, + { + Self { + long: long.to_string(), + short: short.to_string(), + } + } +} + +impl Error for DatabaseError {} + +impl std::fmt::Display for DatabaseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.long) + } +} + +impl IntoFieldError for DatabaseError { + fn into_field_error(self) -> juniper::FieldError { + FieldError::new( + self.long, + graphql_value!({ "error": "Connection refused" }), + ) + } +} + macro_rules! find_element { ($conn:expr,$dsl:ident,$type:ty,$value:expr,$errmsg:expr) => { if let Ok(val) = $conn { @@ -27,8 +68,6 @@ macro_rules! find_element { }; } -use diesel::prelude::*; - #[derive(Debug, Clone)] pub struct Database { conn: Pool>, @@ -59,17 +98,44 @@ impl Database { fn conn( &self, - ) -> Result>, ()> { - self.conn - .get() - .map_err(|e| info!("Failed to connect to database: {:?}", e)) + ) -> Result>, DatabaseError> + { + self.conn.get().map_err(|e| { + DatabaseError::new( + format!("Failed to connect to database: {:?}", e), + "Database connection error", + ) + }) } - pub fn all_languages(&self) -> Result, ()> { + pub fn all_languages(&self) -> Result, DatabaseError> { use self::schema::languages::dsl::languages; - languages.load::(&mut self.conn()?).map_err(|e| { - info!("Failed to retrieve languages from database: {:?}", e); - }) + languages + .load::(&mut self.conn()?) + .map_err(|e| { + info!("Failed to retrieve languages from database: {:?}", e); + }) + .map_err(|e| { + DatabaseError::new( + format!("Failed to retrieve languages: {:?}", e), + "Failed to retrieve languages", + ) + }) + } + + pub fn all_users(&self) -> Result, DatabaseError> { + use self::schema::users::dsl::users; + users + .load::(&mut self.conn()?) + .map_err(|e| { + info!("Failed to retrieve languages from database: {:?}", e); + }) + .map_err(|e| { + DatabaseError::new( + format!("Failed to retrieve languages: {:?}", e), + "Failed to retrieve languages", + ) + }) } pub fn language(&self, name: &str, owner: &str) -> Option { @@ -105,6 +171,42 @@ impl Database { ) } + pub fn insert_user( + &self, + username: String, + id: String, + ) -> Result { + use self::schema::users::dsl::users; + let user = User { id, username }; + match insert_into(users).values(user.clone()).execute( + &mut self.conn().map_err(|e| { + DatabaseError::new( + format!("Failed to connect to the database: {:?}", e), + "Connection error", + ) + })?, + ) { + Ok(_) => Ok(user), + Err(e) => Err(DatabaseError { + long: format!("Failed to insert user {:?}: {:?}", user, e), + short: "Data insertion error".to_string(), + }), + } + } + + pub fn delete_user(&self, id: &str) -> Result<(), DatabaseError> { + use self::schema::users::dsl::users; + match diesel::delete(users.find(id.to_string())) + .execute(&mut self.conn()?) + { + Ok(_) => Ok(()), + Err(e) => Err(DatabaseError::new( + format!("Failed to delete user {}: {:?}", id, e), + "User deletion error", + )), + } + } + pub fn word_id(&self, id: &str) -> Option { use self::schema::words::dsl; if let Ok(conn) = &mut self.conn() { diff --git a/src/db/models/users.rs b/src/db/models/users.rs index 3427064..8a6e425 100644 --- a/src/db/models/users.rs +++ b/src/db/models/users.rs @@ -5,8 +5,8 @@ use crate::graphql::Context; #[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)] pub struct User { - id: String, - username: String, + pub id: String, + pub username: String, } #[juniper::graphql_object(Context = Context)] diff --git a/src/graphql/mutation.rs b/src/graphql/mutation.rs index 2fc5d25..7273bcf 100644 --- a/src/graphql/mutation.rs +++ b/src/graphql/mutation.rs @@ -1,16 +1,52 @@ +use juniper::FieldResult; + +use crate::db::{models::users::User, DatabaseError}; + use super::Context; pub struct Mutation; #[juniper::graphql_object(Context = Context)] impl Mutation { - fn api_version( - context: &Context, - ) -> String { + fn api_version(context: &Context) -> String { if context.user_auth { "0.1 (authentified)" } else { "0.1 (not authentified)" - }.into() + } + .into() + } + + pub fn db_only_new_user( + context: &Context, + username: String, + id: String, + admin_key: String, + ) -> FieldResult { + if admin_key == context.other_vars.admin_key { + context + .db + .insert_user(username, id) + .map_err(std::convert::Into::into) + } else { + Err(DatabaseError::new("Invalid admin key", "Invalid admin key") + .into()) + } + } + + pub fn db_only_delete_user( + context: &Context, + id: String, + admin_key: String, + ) -> FieldResult { + if admin_key == context.other_vars.admin_key { + match context.db.delete_user(&id) { + Ok(_) => Ok("done".into()), + Err(e) => Err(e.into()), + } + } else { + Err(DatabaseError::new("Invalid admin key", "Invalid admin key") + .into()) + } } } diff --git a/src/graphql/query.rs b/src/graphql/query.rs index 859970e..f5bf48e 100644 --- a/src/graphql/query.rs +++ b/src/graphql/query.rs @@ -1,8 +1,12 @@ +use juniper::FieldResult; + use super::Context; -use crate::db::models::{languages::Language, users::User, words::Word}; +use crate::db::{models::{languages::Language, users::User, words::Word}, DatabaseError}; use std::str::FromStr; +use std::convert::Into; + #[derive(Debug)] pub struct Query; @@ -16,6 +20,14 @@ impl Query { context.db.all_languages().unwrap() } + fn all_users(context: &Context, admin_key: String) -> FieldResult> { + if admin_key == context.other_vars.admin_key { + context.db.all_users().map_err(Into::into) + } else { + Err(DatabaseError::new("Invalid admin key", "Invalid admin key").into()) + } + } + #[graphql( description = "Retrieve a specific language from its name and its owner's id", arguments(