pub mod models; 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::result::Error; use diesel::{insert_into, prelude::*}; use dotenvy::dotenv; use juniper::{graphql_value, DefaultScalarValue, FieldError, IntoFieldError}; use std::env; use tracing::info; #[derive(Debug)] pub struct DatabaseError { long: String, #[allow(dead_code)] short: String, } impl DatabaseError { #[allow(clippy::needless_pass_by_value)] pub fn new(long: S, short: T) -> Self where T: ToString, S: ToString, { Self { long: long.to_string(), short: short.to_string(), } } } impl std::error::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 { let short = self.short; FieldError::new(self.long, graphql_value!({ "error": short })) } } #[derive(Debug, Clone)] pub struct Database { conn: Pool>, } impl juniper::Context for Database {} impl Default for Database { fn default() -> Self { Self { conn: Database::get_connection_pool(), } } } impl Database { pub fn get_connection_pool() -> Pool> { dotenv().ok(); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set!"); info!("Connecting to {}", database_url); let manager = ConnectionManager::::new(database_url); Pool::builder() .test_on_check_out(true) .build(manager) .expect("Could not build connection pool") } fn conn( &self, ) -> 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, DatabaseError> { use self::schema::languages::dsl::languages; 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| { DatabaseError::new( format!("Failed to retrieve languages: {e:?}"), "Failed to retrieve languages", ) }) } pub fn find_language( &self, query: &str, ) -> Result, DatabaseError> { use self::schema::languages::dsl; dsl::languages .filter(dsl::name.ilike(format!("%{query}%"))) .load::(&mut self.conn()?) .map_err(|e| { DatabaseError::new( format!( "Failed to retrieve languages with query {query}: {e:?}" ), "Failed to retrieve languages", ) }) } pub fn find_user(&self, query: &str) -> Result, DatabaseError> { use self::schema::users::dsl; dsl::users .filter(dsl::username.ilike(format!("%{query}%"))) .load::(&mut self.conn()?) .map_err(|e| { DatabaseError::new( format!( "Failed to retrieve users with query {query}: {e:?}" ), "Failed to retrieve languages", ) }) } pub fn language( &self, name: &str, owner: &str, ) -> Result, DatabaseError> { use self::schema::languages::dsl; match dsl::languages .filter(dsl::name.eq(name)) .filter(dsl::owner.eq(owner)) .first(&mut self.conn()?) { Ok(val) => Ok(Some(val)), Err(Error::NotFound) => Ok(None), Err(e) => Err(DatabaseError::new( format!( "Failed to find language {name} belonging to {owner}: {e:?}" ), "Database error", )), } } pub fn user(&self, id: &str) -> Result, DatabaseError> { use self::schema::users::dsl::users; match users.find(id).first::(&mut self.conn()?) { Ok(val) => Ok(Some(val)), Err(Error::NotFound) => Ok(None), Err(e) => Err(DatabaseError::new( format!("Failed to retrieve user {id} from database: {e:?}"), "Database Error", )), } } 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: uuid::Uuid) -> Result, DatabaseError> { use self::schema::words::dsl; match dsl::words.find(id).first::(&mut self.conn()?) { Ok(val) => Ok(Some(val)), Err(Error::NotFound) => Ok(None), Err(e) => Err(DatabaseError::new( format!("Failed to retrieve word {id} from database: {e:?}"), "Database Error", )), } } pub fn words( &self, language: uuid::Uuid, word: &str, ) -> Result, DatabaseError> { use self::schema::words::dsl; dsl::words .filter(dsl::language.eq(language)) .filter(dsl::norm.eq(word)) .load::(&mut self.conn()?) .map_err(|e| { DatabaseError::new( format!( "Failed to retrieve word {word} from language {language}: {e:?}" ), "Failed to retrieve languages", ) }) } pub fn find_word( &self, language: uuid::Uuid, query: &str, ) -> Result, DatabaseError> { use self::schema::words::dsl; dsl::words .filter(dsl::language.eq(language)) .filter(dsl::norm.ilike(format!("%{query}%"))) .load::(&mut self.conn()?) .map_err(|e| { DatabaseError::new( format!( "Failed to retrieve words from language {language} with query {query}: {e:?}" ), "Failed to retrieve languages", ) }) } }