use super::super::schema; use crate::{ db::{Database, DatabaseError}, graphql::Context, }; use diesel::prelude::*; use juniper::{FieldResult, GraphQLEnum}; use schema::{wordrelation, words, wordlearning}; use tracing::info; use uuid::Uuid; use std::convert::Into; use super::languages::Language; #[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq)] #[DieselTypePath = "crate::db::schema::sql_types::Wordrelationship"] pub enum WordRelationship { Definition, Related, } #[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq, juniper::GraphQLEnum)] #[DieselTypePath = "crate::db::schema::sql_types::Wordlearningstatus"] pub enum WordLearningStatus { Learning, Learned } #[derive( diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq, GraphQLEnum, )] #[DieselTypePath = "crate::db::schema::sql_types::Partofspeech"] pub enum PartOfSpeech { Adjective, Adposition, Adverb, Auxilliary, #[graphql(name = "COORDINATING_CONJUNCTION")] CoordConj, Determiner, Interjection, Noun, Numeral, Particle, Pronoun, #[graphql(name = "PROPER_NOUN")] ProperNoun, Punctuation, #[graphql(name = "SUBORDINATING_CONJUNCTION")] SubjConj, Symbol, Verb, Other, } #[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)] pub struct Word { id: Uuid, norm: String, native: Option, lemma: Option, language: uuid::Uuid, partofspeech: PartOfSpeech, audio: Option, video: Option, image: Option, description: Option, etymology: Option, lusage: Option, morphology: Option, } impl Word { fn relationship( &self, db: &Database, relationship: WordRelationship, ) -> Result, DatabaseError> { use schema::wordrelation::dsl; match &mut db.conn() { Ok(conn) => Ok(dsl::wordrelation .filter(dsl::wordsource.eq(self.id)) .filter(dsl::relationship.eq(relationship)) .load::(conn) .map_err(|e| { DatabaseError::new( format!("Failed to retrieve word relations: {e:?}"), "Database reading failed", ) })? .into_iter() .flat_map(|word| { use schema::words::dsl; dsl::words.find(word.wordtarget).first::(conn) }) .collect::>()), Err(e) => Err(DatabaseError::new( format!("Failed to connect to the database: {e:?}"), "Database connection error", )), } } } #[juniper::graphql_object(Context = Context)] impl Word { #[graphql(description = "Normal form of the word")] fn norm(&self) -> String { self.norm.clone() } #[graphql(description = "Native representation of the word")] fn native(&self) -> Option { self.native.clone() } #[graphql(description = "Base form of the current word")] fn lemma(&self, context: &Context) -> Option { use schema::words::dsl; match self.lemma { Some(lemma) => match &mut context.db.conn() { Ok(conn) => match dsl::words.find(lemma).first::(conn) { Ok(word) => Some(word), Err(e) => { info!( "Failed to retrieve lemma {} of word {}: {:?}", lemma, self.norm, e ); None } }, Err(e) => { info!("Could not connect to the database: {:?}", e); None } }, None => None, } } #[graphql(description = "Language to which the word belongs")] fn language(&self, context: &Context) -> FieldResult { use schema::languages::dsl; use std::convert::Into; dsl::languages .find(self.language) .first::(&mut context.db.conn()?) .map_err(|e| DatabaseError::new( format!( "Failed to retrieve language {} of word {} from database: {e:?}", self.language, self.norm ), "Database Error" ).into()) } #[graphql( name = "partOfSpeech", description = "Part of speech the word belongs to" )] fn part_of_speech(&self) -> PartOfSpeech { self.partofspeech.clone() } #[graphql(description = "Link to an audio file related to the word")] fn audio(&self) -> Option { self.audio.clone() } #[graphql(description = "Link to an video file related to the word")] fn video(&self) -> Option { self.video.clone() } #[graphql(description = "Link to an image file related to the word")] fn image(&self) -> Option { self.image.clone() } #[graphql(description = "Etymology of the word, can be in Markdown format")] fn etymology(&self) -> Option { self.etymology.clone() } #[graphql(description = "Usage of the word, can be in Markdown format")] fn usage(&self) -> Option { self.lusage.clone() } #[graphql( description = "Morphology of the word, can be in Markdown format" )] fn morphology(&self) -> Option { self.morphology.clone() } #[graphql( name = "related", description = "Words related to the current word" )] fn related_words(&self, context: &Context) -> FieldResult> { self.relationship(&context.db, WordRelationship::Related) .map_err(Into::into) } #[graphql( name = "definitions", description = "Words that define the current word" )] fn definitions(&self, context: &Context) -> FieldResult> { self.relationship(&context.db, WordRelationship::Definition) .map_err(Into::into) } } #[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)] #[diesel(table_name = wordrelation)] pub struct WordRelation { id: i32, wordsource: Uuid, wordtarget: Uuid, relationship: WordRelationship, } #[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)] #[diesel(table_name = wordlearning)] pub struct WordLearning { pub id: i32, pub word: Uuid, pub userid: String, pub status: WordLearningStatus }