Manually add users to database, better handling of errors in API
All checks were successful
continuous-integration/drone/push Build is passing

Also manually remove users from database, and list them only as admin
This commit is contained in:
Lucien Cartier-Tilet 2023-01-16 00:09:50 +01:00
parent 8c62727ec9
commit 425e00acc1
Signed by: phundrak
GPG Key ID: BD7789E705CB8DCA
4 changed files with 167 additions and 17 deletions

View File

@ -4,12 +4,53 @@ pub mod schema;
use self::models::languages::Language; use self::models::languages::Language;
use self::models::users::User; use self::models::users::User;
use self::models::words::Word; use self::models::words::Word;
use diesel::pg::PgConnection; use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use diesel::{insert_into, prelude::*};
use dotenvy::dotenv; use dotenvy::dotenv;
use juniper::{graphql_value, DefaultScalarValue, FieldError, IntoFieldError};
use std::env; use std::env;
use std::error::Error;
use tracing::info; use tracing::info;
#[derive(Debug)]
pub struct DatabaseError {
long: String,
short: String,
}
impl DatabaseError {
pub fn new<S, T>(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<DefaultScalarValue> {
FieldError::new(
self.long,
graphql_value!({ "error": "Connection refused" }),
)
}
}
macro_rules! find_element { macro_rules! find_element {
($conn:expr,$dsl:ident,$type:ty,$value:expr,$errmsg:expr) => { ($conn:expr,$dsl:ident,$type:ty,$value:expr,$errmsg:expr) => {
if let Ok(val) = $conn { if let Ok(val) = $conn {
@ -27,8 +68,6 @@ macro_rules! find_element {
}; };
} }
use diesel::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Database { pub struct Database {
conn: Pool<ConnectionManager<PgConnection>>, conn: Pool<ConnectionManager<PgConnection>>,
@ -59,17 +98,44 @@ impl Database {
fn conn( fn conn(
&self, &self,
) -> Result<PooledConnection<ConnectionManager<PgConnection>>, ()> { ) -> Result<PooledConnection<ConnectionManager<PgConnection>>, DatabaseError>
self.conn {
.get() self.conn.get().map_err(|e| {
.map_err(|e| info!("Failed to connect to database: {:?}", e)) DatabaseError::new(
format!("Failed to connect to database: {:?}", e),
"Database connection error",
)
})
} }
pub fn all_languages(&self) -> Result<Vec<Language>, ()> { pub fn all_languages(&self) -> Result<Vec<Language>, DatabaseError> {
use self::schema::languages::dsl::languages; use self::schema::languages::dsl::languages;
languages.load::<Language>(&mut self.conn()?).map_err(|e| { languages
.load::<Language>(&mut self.conn()?)
.map_err(|e| {
info!("Failed to retrieve languages from database: {:?}", 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<Vec<User>, DatabaseError> {
use self::schema::users::dsl::users;
users
.load::<User>(&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<Language> { pub fn language(&self, name: &str, owner: &str) -> Option<Language> {
@ -105,6 +171,42 @@ impl Database {
) )
} }
pub fn insert_user(
&self,
username: String,
id: String,
) -> Result<User, DatabaseError> {
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<Word> { pub fn word_id(&self, id: &str) -> Option<Word> {
use self::schema::words::dsl; use self::schema::words::dsl;
if let Ok(conn) = &mut self.conn() { if let Ok(conn) = &mut self.conn() {

View File

@ -5,8 +5,8 @@ use crate::graphql::Context;
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)] #[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
pub struct User { pub struct User {
id: String, pub id: String,
username: String, pub username: String,
} }
#[juniper::graphql_object(Context = Context)] #[juniper::graphql_object(Context = Context)]

View File

@ -1,16 +1,52 @@
use juniper::FieldResult;
use crate::db::{models::users::User, DatabaseError};
use super::Context; use super::Context;
pub struct Mutation; pub struct Mutation;
#[juniper::graphql_object(Context = Context)] #[juniper::graphql_object(Context = Context)]
impl Mutation { impl Mutation {
fn api_version( fn api_version(context: &Context) -> String {
context: &Context,
) -> String {
if context.user_auth { if context.user_auth {
"0.1 (authentified)" "0.1 (authentified)"
} else { } else {
"0.1 (not authentified)" "0.1 (not authentified)"
}.into() }
.into()
}
pub fn db_only_new_user(
context: &Context,
username: String,
id: String,
admin_key: String,
) -> FieldResult<User> {
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<String> {
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())
}
} }
} }

View File

@ -1,8 +1,12 @@
use juniper::FieldResult;
use super::Context; 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::str::FromStr;
use std::convert::Into;
#[derive(Debug)] #[derive(Debug)]
pub struct Query; pub struct Query;
@ -16,6 +20,14 @@ impl Query {
context.db.all_languages().unwrap() context.db.all_languages().unwrap()
} }
fn all_users(context: &Context, admin_key: String) -> FieldResult<Vec<User>> {
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( #[graphql(
description = "Retrieve a specific language from its name and its owner's id", description = "Retrieve a specific language from its name and its owner's id",
arguments( arguments(