2023-01-03 15:11:43 +00:00
|
|
|
pub mod models;
|
|
|
|
pub mod schema;
|
2023-01-04 18:31:52 +00:00
|
|
|
|
|
|
|
use self::models::languages::Language;
|
2023-01-04 21:16:48 +00:00
|
|
|
use self::models::users::User;
|
|
|
|
use self::models::words::Word;
|
2023-01-15 23:09:50 +00:00
|
|
|
|
2023-01-04 18:31:52 +00:00
|
|
|
use diesel::pg::PgConnection;
|
|
|
|
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
|
2023-01-16 23:58:01 +00:00
|
|
|
use diesel::result::Error;
|
2023-01-15 23:09:50 +00:00
|
|
|
use diesel::{insert_into, prelude::*};
|
|
|
|
|
2023-01-04 18:31:52 +00:00
|
|
|
use dotenvy::dotenv;
|
2023-01-15 23:09:50 +00:00
|
|
|
use juniper::{graphql_value, DefaultScalarValue, FieldError, IntoFieldError};
|
2023-01-04 18:31:52 +00:00
|
|
|
use std::env;
|
|
|
|
use tracing::info;
|
|
|
|
|
2023-01-15 23:09:50 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct DatabaseError {
|
|
|
|
long: String,
|
2023-01-15 23:22:58 +00:00
|
|
|
#[allow(dead_code)]
|
2023-01-15 23:09:50 +00:00
|
|
|
short: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DatabaseError {
|
2023-01-15 23:22:58 +00:00
|
|
|
#[allow(clippy::needless_pass_by_value)]
|
2023-01-15 23:09:50 +00:00
|
|
|
pub fn new<S, T>(long: S, short: T) -> Self
|
|
|
|
where
|
|
|
|
T: ToString,
|
|
|
|
S: ToString,
|
|
|
|
{
|
|
|
|
Self {
|
|
|
|
long: long.to_string(),
|
|
|
|
short: short.to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-16 23:58:01 +00:00
|
|
|
impl std::error::Error for DatabaseError {}
|
2023-01-15 23:09:50 +00:00
|
|
|
|
|
|
|
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" }),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-15 17:10:51 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2023-01-04 18:31:52 +00:00
|
|
|
pub struct Database {
|
|
|
|
conn: Pool<ConnectionManager<PgConnection>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl juniper::Context for Database {}
|
|
|
|
|
2023-01-04 21:16:48 +00:00
|
|
|
impl Default for Database {
|
|
|
|
fn default() -> Self {
|
2023-01-04 18:31:52 +00:00
|
|
|
Self {
|
|
|
|
conn: Database::get_connection_pool(),
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 21:16:48 +00:00
|
|
|
}
|
2023-01-04 18:31:52 +00:00
|
|
|
|
2023-01-04 21:16:48 +00:00
|
|
|
impl Database {
|
2023-01-04 18:31:52 +00:00
|
|
|
pub fn get_connection_pool() -> Pool<ConnectionManager<PgConnection>> {
|
|
|
|
dotenv().ok();
|
|
|
|
let database_url =
|
|
|
|
env::var("DATABASE_URL").expect("DATABASE_URL must be set!");
|
|
|
|
info!("Connecting to {}", database_url);
|
|
|
|
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
|
|
|
Pool::builder()
|
|
|
|
.test_on_check_out(true)
|
|
|
|
.build(manager)
|
|
|
|
.expect("Could not build connection pool")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn conn(
|
|
|
|
&self,
|
2023-01-15 23:09:50 +00:00
|
|
|
) -> Result<PooledConnection<ConnectionManager<PgConnection>>, DatabaseError>
|
|
|
|
{
|
|
|
|
self.conn.get().map_err(|e| {
|
|
|
|
DatabaseError::new(
|
|
|
|
format!("Failed to connect to database: {:?}", e),
|
|
|
|
"Database connection error",
|
|
|
|
)
|
|
|
|
})
|
2023-01-04 18:31:52 +00:00
|
|
|
}
|
|
|
|
|
2023-01-15 23:09:50 +00:00
|
|
|
pub fn all_languages(&self) -> Result<Vec<Language>, DatabaseError> {
|
2023-01-04 18:31:52 +00:00
|
|
|
use self::schema::languages::dsl::languages;
|
2023-01-15 23:09:50 +00:00
|
|
|
languages
|
|
|
|
.load::<Language>(&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<Vec<User>, DatabaseError> {
|
|
|
|
use self::schema::users::dsl::users;
|
2023-01-16 23:40:36 +00:00
|
|
|
users.load::<User>(&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<Vec<Language>, DatabaseError> {
|
|
|
|
use self::schema::languages::dsl;
|
|
|
|
dsl::languages
|
|
|
|
.filter(dsl::name.ilike(format!("%{}%", query)))
|
|
|
|
.load::<Language>(&mut self.conn()?)
|
2023-01-15 23:09:50 +00:00
|
|
|
.map_err(|e| {
|
2023-01-16 23:40:36 +00:00
|
|
|
DatabaseError::new(
|
|
|
|
format!(
|
|
|
|
"Failed to retrieve languages with query {}: {:?}",
|
|
|
|
query, e
|
|
|
|
),
|
|
|
|
"Failed to retrieve languages",
|
|
|
|
)
|
2023-01-15 23:09:50 +00:00
|
|
|
})
|
2023-01-16 23:40:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn find_user(&self, query: &str) -> Result<Vec<User>, DatabaseError> {
|
|
|
|
use self::schema::users::dsl;
|
|
|
|
dsl::users
|
|
|
|
.filter(dsl::username.ilike(format!("%{}%", query)))
|
|
|
|
.load::<User>(&mut self.conn()?)
|
2023-01-15 23:09:50 +00:00
|
|
|
.map_err(|e| {
|
|
|
|
DatabaseError::new(
|
2023-01-16 23:40:36 +00:00
|
|
|
format!(
|
|
|
|
"Failed to retrieve users with query {}: {:?}",
|
|
|
|
query, e
|
|
|
|
),
|
2023-01-15 23:09:50 +00:00
|
|
|
"Failed to retrieve languages",
|
|
|
|
)
|
|
|
|
})
|
2023-01-04 18:31:52 +00:00
|
|
|
}
|
|
|
|
|
2023-01-16 23:58:01 +00:00
|
|
|
pub fn language(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
owner: &str,
|
|
|
|
) -> Result<Option<Language>, DatabaseError> {
|
2023-01-04 21:16:48 +00:00
|
|
|
use self::schema::languages::dsl;
|
2023-01-16 23:58:01 +00:00
|
|
|
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 {} belonging to {}: {:?}",
|
|
|
|
name, owner, e
|
|
|
|
),
|
|
|
|
"Database error",
|
|
|
|
)),
|
2023-01-04 21:16:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-16 23:58:01 +00:00
|
|
|
pub fn user(&self, id: &str) -> Result<Option<User>, DatabaseError> {
|
2023-01-04 21:16:48 +00:00
|
|
|
use self::schema::users::dsl::users;
|
2023-01-16 23:58:01 +00:00
|
|
|
match users.find(id).first::<User>(&mut self.conn()?) {
|
|
|
|
Ok(val) => Ok(Some(val)),
|
|
|
|
Err(Error::NotFound) => Ok(None),
|
|
|
|
Err(e) => Err(DatabaseError::new(
|
|
|
|
format!(
|
|
|
|
"Failed to retrieve user {} from database: {:?}",
|
|
|
|
id, e
|
|
|
|
),
|
|
|
|
"Database Error",
|
|
|
|
)),
|
|
|
|
}
|
2023-01-04 21:16:48 +00:00
|
|
|
}
|
|
|
|
|
2023-01-15 23:09:50 +00:00
|
|
|
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",
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-16 23:58:01 +00:00
|
|
|
pub fn word_id(&self, id: &str) -> Result<Option<Word>, DatabaseError> {
|
2023-01-04 21:16:48 +00:00
|
|
|
use self::schema::words::dsl;
|
2023-01-16 23:58:01 +00:00
|
|
|
match dsl::words.find(id).first::<Word>(&mut self.conn()?) {
|
|
|
|
Ok(val) => Ok(Some(val)),
|
|
|
|
Err(Error::NotFound) => Ok(None),
|
|
|
|
Err(e) => Err(DatabaseError::new(
|
|
|
|
format!(
|
|
|
|
"Failed to retrieve word {} from database: {:?}",
|
|
|
|
id, e
|
|
|
|
),
|
|
|
|
"Database Error",
|
|
|
|
)),
|
2023-01-04 21:16:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-16 23:40:36 +00:00
|
|
|
pub fn words(
|
|
|
|
&self,
|
|
|
|
language: uuid::Uuid,
|
|
|
|
word: &str,
|
|
|
|
) -> Result<Vec<Word>, DatabaseError> {
|
2023-01-04 21:16:48 +00:00
|
|
|
use self::schema::words::dsl;
|
2023-01-16 23:40:36 +00:00
|
|
|
dsl::words
|
|
|
|
.filter(dsl::language.eq(language))
|
|
|
|
.filter(dsl::norm.eq(word))
|
|
|
|
.load::<Word>(&mut self.conn()?)
|
|
|
|
.map_err(|e| {
|
|
|
|
DatabaseError::new(
|
|
|
|
format!(
|
|
|
|
"Failed to retrieve word {} from language {}: {:?}",
|
2023-01-04 21:16:48 +00:00
|
|
|
word, language, e
|
2023-01-16 23:40:36 +00:00
|
|
|
),
|
|
|
|
"Failed to retrieve languages",
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn find_word(
|
|
|
|
&self,
|
|
|
|
language: uuid::Uuid,
|
|
|
|
query: &str,
|
|
|
|
) -> Result<Vec<Word>, DatabaseError> {
|
|
|
|
use self::schema::words::dsl;
|
|
|
|
dsl::words
|
|
|
|
.filter(dsl::language.eq(language))
|
|
|
|
.filter(dsl::norm.ilike(format!("%{}%", query)))
|
|
|
|
.load::<Word>(&mut self.conn()?)
|
|
|
|
.map_err(|e| {
|
|
|
|
DatabaseError::new(
|
|
|
|
format!(
|
|
|
|
"Failed to retrieve words from language {} with query {}: {:?}",
|
|
|
|
language,
|
|
|
|
query, e
|
|
|
|
),
|
|
|
|
"Failed to retrieve languages",
|
|
|
|
)
|
|
|
|
})
|
2023-01-04 18:31:52 +00:00
|
|
|
}
|
|
|
|
}
|