Fragment graphql module, add Appwrite vars to context
This commit is contained in:
parent
34e28384ce
commit
b20fb5f079
@ -26,6 +26,9 @@ uuid = { version = "1.2.2", features = ["v4", "fast-rng", "macro-diagnostics", "
|
||||
rocket = "0.5.0-rc.2"
|
||||
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "c17e814" }
|
||||
|
||||
# Web requests
|
||||
reqwest = { version = "0.11.13", features = ["serde_json", "json", "gzip"] }
|
||||
|
||||
# GraphQL
|
||||
juniper = "0.15.10"
|
||||
juniper_rocket = "0.8.2"
|
||||
@ -33,3 +36,6 @@ juniper_rocket = "0.8.2"
|
||||
# logging
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.3.16"
|
||||
|
||||
# Error handling
|
||||
color-eyre = "0.6.2"
|
||||
|
88
src/appwrite.rs
Normal file
88
src/appwrite.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use rocket::serde::Deserialize;
|
||||
|
||||
macro_rules! from_env {
|
||||
($varname:expr) => {
|
||||
std::env::var($varname)
|
||||
.expect(format!("{} must be set!", $varname).as_str())
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
|
||||
pub struct APVariables {
|
||||
pub endpoint: String,
|
||||
pub project: String,
|
||||
pub api_key: String,
|
||||
}
|
||||
|
||||
impl APVariables {
|
||||
pub async fn check_session(
|
||||
&self,
|
||||
session_id: String,
|
||||
user_id: String,
|
||||
) -> Result<bool> {
|
||||
let client = reqwest::Client::new();
|
||||
let url = format!("{}/users/{}/sessions", self.endpoint, user_id);
|
||||
let response = client
|
||||
.get(url)
|
||||
.header("X-Appwrite-Key", self.api_key.clone())
|
||||
.header("X-Appwrite-Project", self.project.clone())
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await?
|
||||
.json::<UserSessions>()
|
||||
.await?;
|
||||
Ok(response.sessions.iter().any(|s| s.id == session_id))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for APVariables {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
endpoint: from_env!("APPWRITE_ENDPOINT"),
|
||||
project: from_env!("APPWRITE_PROJECT"),
|
||||
api_key: from_env!("APPWRITE_API_KEY"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
struct UserSessions {
|
||||
total: i64,
|
||||
sessions: Vec<Sessions>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
struct Sessions {
|
||||
#[serde(rename = "$id")]
|
||||
id: String,
|
||||
#[serde(rename = "$createdAt")]
|
||||
created_at: String,
|
||||
user_id: String,
|
||||
expire: String,
|
||||
provider: String,
|
||||
provider_uid: String,
|
||||
provider_access_token: String,
|
||||
provider_access_token_expiry: String,
|
||||
provider_refresh_token: String,
|
||||
ip: String,
|
||||
os_code: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
client_type: String,
|
||||
client_code: String,
|
||||
client_name: String,
|
||||
client_version: String,
|
||||
client_engine: String,
|
||||
client_engine_version: String,
|
||||
device_name: String,
|
||||
device_brand: String,
|
||||
device_model: String,
|
||||
country_code: String,
|
||||
country_name: String,
|
||||
current: bool,
|
||||
}
|
@ -29,6 +29,7 @@ macro_rules! find_element {
|
||||
|
||||
use diesel::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Database {
|
||||
conn: Pool<ConnectionManager<PgConnection>>,
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::db::Database;
|
||||
use crate::{db::Database, graphql::Context};
|
||||
use diesel::prelude::*;
|
||||
use juniper::GraphQLEnum;
|
||||
use tracing::info;
|
||||
@ -63,11 +63,11 @@ pub struct Language {
|
||||
impl Language {
|
||||
fn relationship(
|
||||
&self,
|
||||
context: &Database,
|
||||
db: &Database,
|
||||
relationship: AgentLanguageRelation,
|
||||
) -> Vec<User> {
|
||||
use schema::langandagents::dsl;
|
||||
match &mut context.conn() {
|
||||
match &mut db.conn() {
|
||||
Ok(conn) => dsl::langandagents
|
||||
.filter(dsl::language.eq(self.id))
|
||||
.filter(dsl::relationship.eq(relationship))
|
||||
@ -97,7 +97,7 @@ impl Language {
|
||||
}
|
||||
}
|
||||
|
||||
#[juniper::graphql_object(Context = Database)]
|
||||
#[juniper::graphql_object(Context = Context)]
|
||||
impl Language {
|
||||
#[graphql(description = "Unique identifier of the language")]
|
||||
fn id(&self) -> String {
|
||||
@ -125,9 +125,9 @@ impl Language {
|
||||
name = "targetLanguage",
|
||||
description = "Languages in which the current language is translated"
|
||||
)]
|
||||
fn target_language(&self, context: &Database) -> Vec<Language> {
|
||||
fn target_language(&self, context: &Context) -> Vec<Language> {
|
||||
use schema::langtranslatesto::dsl;
|
||||
match &mut context.conn() {
|
||||
match &mut context.db.conn() {
|
||||
Ok(conn) => dsl::langtranslatesto
|
||||
.filter(dsl::langfrom.eq(self.id))
|
||||
.load::<LangTranslatesTo>(conn)
|
||||
@ -187,9 +187,9 @@ impl Language {
|
||||
#[graphql(
|
||||
description = "User with administrative rights over the language"
|
||||
)]
|
||||
fn owner(&self, context: &Database) -> User {
|
||||
fn owner(&self, context: &Context) -> User {
|
||||
use schema::users::dsl;
|
||||
match &mut context.conn() {
|
||||
match &mut context.db.conn() {
|
||||
Ok(conn) => dsl::users
|
||||
.find(self.owner.clone())
|
||||
.first::<User>(conn)
|
||||
@ -206,15 +206,15 @@ impl Language {
|
||||
#[graphql(
|
||||
description = "People who participate in the elaboration of the language's dictionary"
|
||||
)]
|
||||
fn authors(&self, context: &Database) -> Vec<User> {
|
||||
self.relationship(context, AgentLanguageRelation::Author)
|
||||
fn authors(&self, context: &Context) -> Vec<User> {
|
||||
self.relationship(&context.db, AgentLanguageRelation::Author)
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "People who can and do redistribute the language's dictionary"
|
||||
)]
|
||||
fn publishers(&self, context: &Database) -> Vec<User> {
|
||||
self.relationship(context, AgentLanguageRelation::Publisher)
|
||||
fn publishers(&self, context: &Context) -> Vec<User> {
|
||||
self.relationship(&context.db, AgentLanguageRelation::Publisher)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
use super::super::schema::{userfollows, users};
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::db::Database;
|
||||
use crate::graphql::Context;
|
||||
|
||||
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
id: String,
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[juniper::graphql_object(Context = Database)]
|
||||
#[juniper::graphql_object(Context = Context)]
|
||||
impl User {
|
||||
#[graphql(description = "Appwrite ID of the user")]
|
||||
pub fn id(&self) -> String {
|
||||
@ -22,9 +22,9 @@ impl User {
|
||||
}
|
||||
|
||||
#[graphql(description = "Who the user follows")]
|
||||
pub fn following(&self, context: &Database) -> Vec<User> {
|
||||
pub fn following(&self, context: &Context) -> Vec<User> {
|
||||
use super::super::schema::{userfollows, users};
|
||||
let conn = &mut context.conn().unwrap();
|
||||
let conn = &mut context.db.conn().unwrap();
|
||||
userfollows::dsl::userfollows
|
||||
.filter(userfollows::dsl::follower.eq(self.id.clone()))
|
||||
.load::<UserFollow>(conn)
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::super::schema;
|
||||
use crate::db::Database;
|
||||
use crate::{db::Database, graphql::Context};
|
||||
use diesel::prelude::*;
|
||||
use juniper::GraphQLEnum;
|
||||
use schema::{wordrelation, words};
|
||||
@ -60,11 +60,11 @@ pub struct Word {
|
||||
impl Word {
|
||||
fn relationship(
|
||||
&self,
|
||||
context: &Database,
|
||||
db: &Database,
|
||||
relationship: WordRelationship,
|
||||
) -> Vec<Word> {
|
||||
use schema::wordrelation::dsl;
|
||||
match &mut context.conn() {
|
||||
match &mut db.conn() {
|
||||
Ok(conn) => dsl::wordrelation
|
||||
.filter(dsl::wordsource.eq(self.norm.clone()))
|
||||
.filter(dsl::relationship.eq(relationship))
|
||||
@ -84,7 +84,7 @@ impl Word {
|
||||
}
|
||||
}
|
||||
|
||||
#[juniper::graphql_object(Context = Database)]
|
||||
#[juniper::graphql_object(Context = Context)]
|
||||
impl Word {
|
||||
#[graphql(description = "Normal form of the word")]
|
||||
fn norm(&self) -> String {
|
||||
@ -97,10 +97,10 @@ impl Word {
|
||||
}
|
||||
|
||||
#[graphql(description = "Base form of the current word")]
|
||||
fn lemma(&self, context: &Database) -> Option<Word> {
|
||||
fn lemma(&self, context: &Context) -> Option<Word> {
|
||||
use schema::words::dsl;
|
||||
match self.lemma.clone() {
|
||||
Some(lemma) => match &mut context.conn() {
|
||||
Some(lemma) => match &mut context.db.conn() {
|
||||
Ok(conn) => {
|
||||
match dsl::words.find(lemma.clone()).first::<Word>(conn) {
|
||||
Ok(word) => Some(word),
|
||||
@ -123,9 +123,9 @@ impl Word {
|
||||
}
|
||||
|
||||
#[graphql(description = "Language to which the word belongs")]
|
||||
fn language(&self, context: &Database) -> Language {
|
||||
fn language(&self, context: &Context) -> Language {
|
||||
use schema::languages::dsl;
|
||||
match &mut context.conn() {
|
||||
match &mut context.db.conn() {
|
||||
Ok(conn) => {
|
||||
match dsl::languages.find(self.language).first::<Language>(conn)
|
||||
{
|
||||
@ -185,16 +185,16 @@ impl Word {
|
||||
name = "related",
|
||||
description = "Words related to the current word"
|
||||
)]
|
||||
fn related_words(&self, context: &Database) -> Vec<Word> {
|
||||
self.relationship(context, WordRelationship::Related)
|
||||
fn related_words(&self, context: &Context) -> Vec<Word> {
|
||||
self.relationship(&context.db, WordRelationship::Related)
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
name = "definitions",
|
||||
description = "Words that define the current word"
|
||||
)]
|
||||
fn definitions(&self, context: &Database) -> Vec<Word> {
|
||||
self.relationship(context, WordRelationship::Definition)
|
||||
fn definitions(&self, context: &Context) -> Vec<Word> {
|
||||
self.relationship(&context.db, WordRelationship::Definition)
|
||||
}
|
||||
}
|
||||
|
||||
|
114
src/graphql.rs
114
src/graphql.rs
@ -1,114 +0,0 @@
|
||||
use rocket::response::content::RawHtml;
|
||||
use rocket::State;
|
||||
|
||||
use juniper::EmptySubscription;
|
||||
use juniper_rocket::{GraphQLRequest, GraphQLResponse};
|
||||
|
||||
use crate::db::models::{languages::Language, users::User, words::Word};
|
||||
use crate::db::Database;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Query;
|
||||
|
||||
#[juniper::graphql_object(Context = Database)]
|
||||
impl Query {
|
||||
#[graphql(
|
||||
name = "allLanguages",
|
||||
description = "Retrieve all languages defined in the database"
|
||||
)]
|
||||
fn all_languages(context: &Database) -> Vec<Language> {
|
||||
context.all_languages().unwrap()
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve a specific language from its name and its owner's id",
|
||||
arguments(
|
||||
name(description = "Name of the language"),
|
||||
owner(description = "ID of the owner of the language")
|
||||
)
|
||||
)]
|
||||
fn language(
|
||||
context: &Database,
|
||||
name: String,
|
||||
owner: String,
|
||||
) -> Option<Language> {
|
||||
context.language(name.as_str(), owner.as_str())
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve a specific user from its id",
|
||||
arguments(id(description = "Appwrite ID of a user"))
|
||||
)]
|
||||
fn user(context: &Database, id: String) -> Option<User> {
|
||||
context.user(id.as_str())
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve a specific word from its id",
|
||||
arguments(id(description = "Unique identifier of a word"))
|
||||
)]
|
||||
fn word(context: &Database, id: String) -> Option<Word> {
|
||||
context.word_id(id.as_str())
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve all words with a set normal form from a set language",
|
||||
arguments(
|
||||
owner(
|
||||
description = "ID of the owner of the language to search a word in"
|
||||
),
|
||||
language(description = "Name of the language to search a word in"),
|
||||
word(description = "Word to search")
|
||||
)
|
||||
)]
|
||||
fn words(
|
||||
context: &Database,
|
||||
language: String,
|
||||
word: String,
|
||||
) -> Vec<Word> {
|
||||
context.words(uuid::Uuid::from_str(&language).unwrap(), word.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mutation;
|
||||
|
||||
#[juniper::graphql_object(Context = Database)]
|
||||
impl Mutation {
|
||||
fn api_version() -> String {
|
||||
"0.1".into()
|
||||
}
|
||||
}
|
||||
|
||||
type Schema =
|
||||
juniper::RootNode<'static, Query, Mutation, EmptySubscription<Database>>;
|
||||
|
||||
pub fn create_schema() -> Schema {
|
||||
Schema::new(Query {}, Mutation {}, EmptySubscription::default())
|
||||
}
|
||||
|
||||
#[rocket::get("/")]
|
||||
pub fn graphiql() -> RawHtml<String> {
|
||||
let graphql_endpoint_url = "/graphql";
|
||||
juniper_rocket::graphiql_source(graphql_endpoint_url, None)
|
||||
}
|
||||
|
||||
#[rocket::get("/graphql?<request>")]
|
||||
pub async fn get_graphql_handler(
|
||||
context: &State<Database>,
|
||||
request: GraphQLRequest,
|
||||
schema: &State<Schema>,
|
||||
) -> GraphQLResponse {
|
||||
request.execute(schema, context).await
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[rocket::post("/graphql", data = "<request>")]
|
||||
pub fn post_graphql_handler(
|
||||
context: &State<Database>,
|
||||
request: GraphQLRequest,
|
||||
schema: &State<Schema>,
|
||||
) -> GraphQLResponse {
|
||||
request.execute_sync(schema, context)
|
||||
}
|
54
src/graphql/mod.rs
Normal file
54
src/graphql/mod.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use rocket::response::content::RawHtml;
|
||||
use rocket::State;
|
||||
|
||||
use juniper::EmptySubscription;
|
||||
use juniper_rocket::{GraphQLRequest, GraphQLResponse};
|
||||
|
||||
use crate::appwrite::APVariables;
|
||||
use crate::db::Database;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Context {
|
||||
pub db: Database,
|
||||
pub appwrite: APVariables,
|
||||
}
|
||||
|
||||
impl juniper::Context for Context {}
|
||||
|
||||
mod query;
|
||||
use query::Query;
|
||||
|
||||
mod mutation;
|
||||
use mutation::Mutation;
|
||||
|
||||
type Schema =
|
||||
juniper::RootNode<'static, Query, Mutation, EmptySubscription<Context>>;
|
||||
|
||||
pub fn create_schema() -> Schema {
|
||||
Schema::new(Query {}, Mutation {}, EmptySubscription::default())
|
||||
}
|
||||
|
||||
#[rocket::get("/")]
|
||||
pub fn graphiql() -> RawHtml<String> {
|
||||
let graphql_endpoint_url = "/graphql";
|
||||
juniper_rocket::graphiql_source(graphql_endpoint_url, None)
|
||||
}
|
||||
|
||||
#[rocket::get("/graphql?<request>")]
|
||||
pub async fn get_graphql_handler(
|
||||
context: &State<Context>,
|
||||
request: GraphQLRequest,
|
||||
schema: &State<Schema>,
|
||||
) -> GraphQLResponse {
|
||||
request.execute(schema, context).await
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[rocket::post("/graphql", data = "<request>")]
|
||||
pub async fn post_graphql_handler(
|
||||
context: &State<Context>,
|
||||
request: GraphQLRequest,
|
||||
schema: &State<Schema>,
|
||||
) -> GraphQLResponse {
|
||||
request.execute(schema, context).await
|
||||
}
|
32
src/graphql/mutation.rs
Normal file
32
src/graphql/mutation.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use super::Context;
|
||||
|
||||
pub struct Mutation;
|
||||
|
||||
#[juniper::graphql_object(Context = Context)]
|
||||
impl Mutation {
|
||||
fn api_version(
|
||||
session_id: Option<String>,
|
||||
user_id: Option<String>,
|
||||
context: &Context,
|
||||
) -> String {
|
||||
"0.1".into()
|
||||
// if session_id.is_some() && user_id.is_some() {
|
||||
// match context
|
||||
// .appwrite
|
||||
// .check_session(session_id.unwrap(), user_id.unwrap())
|
||||
// {
|
||||
// Ok(true) => "0.1 (authentified)".into(),
|
||||
// Ok(false) => "0.1 (not authentified)".into(),
|
||||
// Err(e) => {
|
||||
// info!(
|
||||
// "Error while checking if the user is connected: {:?}",
|
||||
// e
|
||||
// );
|
||||
// "0.1 (auth failed)"
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// "0.1 (not authentified)"
|
||||
// }
|
||||
}
|
||||
}
|
65
src/graphql/query.rs
Normal file
65
src/graphql/query.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use super::Context;
|
||||
use crate::db::models::{languages::Language, users::User, words::Word};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Query;
|
||||
|
||||
#[juniper::graphql_object(Context = Context)]
|
||||
impl Query {
|
||||
#[graphql(
|
||||
name = "allLanguages",
|
||||
description = "Retrieve all languages defined in the database"
|
||||
)]
|
||||
fn all_languages(context: &Context) -> Vec<Language> {
|
||||
context.db.all_languages().unwrap()
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve a specific language from its name and its owner's id",
|
||||
arguments(
|
||||
name(description = "Name of the language"),
|
||||
owner(description = "ID of the owner of the language")
|
||||
)
|
||||
)]
|
||||
fn language(
|
||||
context: &Context,
|
||||
name: String,
|
||||
owner: String,
|
||||
) -> Option<Language> {
|
||||
context.db.language(name.as_str(), owner.as_str())
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve a specific user from its id",
|
||||
arguments(id(description = "Appwrite ID of a user"))
|
||||
)]
|
||||
fn user(context: &Context, id: String) -> Option<User> {
|
||||
context.db.user(id.as_str())
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve a specific word from its id",
|
||||
arguments(id(description = "Unique identifier of a word"))
|
||||
)]
|
||||
fn word(context: &Context, id: String) -> Option<Word> {
|
||||
context.db.word_id(id.as_str())
|
||||
}
|
||||
|
||||
#[graphql(
|
||||
description = "Retrieve all words with a set normal form from a set language",
|
||||
arguments(
|
||||
owner(
|
||||
description = "ID of the owner of the language to search a word in"
|
||||
),
|
||||
language(description = "Name of the language to search a word in"),
|
||||
word(description = "Word to search")
|
||||
)
|
||||
)]
|
||||
fn words(context: &Context, language: String, word: String) -> Vec<Word> {
|
||||
context
|
||||
.db
|
||||
.words(uuid::Uuid::from_str(&language).unwrap(), word.as_str())
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#![warn(clippy::style, clippy::pedantic)]
|
||||
|
||||
mod appwrite;
|
||||
mod db;
|
||||
mod graphql;
|
||||
|
||||
@ -49,6 +50,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
create_schema, get_graphql_handler, graphiql, post_graphql_handler,
|
||||
};
|
||||
|
||||
color_eyre::install()?;
|
||||
setup_logging();
|
||||
|
||||
info!("Reading environment variables");
|
||||
@ -60,7 +62,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
#[allow(clippy::let_underscore_drop, clippy::no_effect_underscore_binding)]
|
||||
let _ = rocket::build()
|
||||
.attach(cors)
|
||||
.manage(db::Database::default())
|
||||
.manage(graphql::Context::default())
|
||||
.manage(create_schema())
|
||||
.mount(
|
||||
"/",
|
||||
|
Loading…
Reference in New Issue
Block a user