274 lines
8.2 KiB
Rust
274 lines
8.2 KiB
Rust
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<S, T>(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<DefaultScalarValue> {
|
|
let short = self.short;
|
|
FieldError::new(self.long, graphql_value!({ "error": short }))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Database {
|
|
conn: Pool<ConnectionManager<PgConnection>>,
|
|
}
|
|
|
|
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<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,
|
|
) -> Result<PooledConnection<ConnectionManager<PgConnection>>, 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<Vec<Language>, DatabaseError> {
|
|
use self::schema::languages::dsl::languages;
|
|
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;
|
|
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()?)
|
|
.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<Vec<User>, DatabaseError> {
|
|
use self::schema::users::dsl;
|
|
dsl::users
|
|
.filter(dsl::username.ilike(format!("%{query}%")))
|
|
.load::<User>(&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<Option<Language>, 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<Option<User>, DatabaseError> {
|
|
use self::schema::users::dsl::users;
|
|
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 {id} from database: {e:?}"),
|
|
"Database Error",
|
|
)),
|
|
}
|
|
}
|
|
|
|
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: uuid::Uuid) -> Result<Option<Word>, DatabaseError> {
|
|
use self::schema::words::dsl;
|
|
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 {id} from database: {e:?}"),
|
|
"Database Error",
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub fn words(
|
|
&self,
|
|
language: uuid::Uuid,
|
|
word: &str,
|
|
) -> Result<Vec<Word>, DatabaseError> {
|
|
use self::schema::words::dsl;
|
|
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 {word} from language {language}: {e:?}"
|
|
),
|
|
"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 {language} with query {query}: {e:?}"
|
|
),
|
|
"Failed to retrieve languages",
|
|
)
|
|
})
|
|
}
|
|
}
|