Initial GraphQL API sort of working
This commit is contained in:
parent
a2a2863d62
commit
8d5e523ab3
12
Cargo.toml
12
Cargo.toml
@ -2,6 +2,12 @@
|
|||||||
name = "ordabok"
|
name = "ordabok"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0"
|
||||||
|
authors = ["Lucien Cartier-Tilet <lucien@phundrak.com>"]
|
||||||
|
homepage = "https://labs.phundrak.com/phundrak/ordabok"
|
||||||
|
repository = "https://labs.phundrak.com/phundrak/ordabok"
|
||||||
|
readme = "README.org"
|
||||||
|
publish = false
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -10,7 +16,7 @@ edition = "2021"
|
|||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
diesel = { version = "2.0", features = ["postgres", "chrono"] }
|
diesel = { version = "2.0", features = ["postgres", "chrono", "r2d2"] }
|
||||||
diesel-derive-enum = { version = "2.0.0-rc.0", features = ["postgres"] }
|
diesel-derive-enum = { version = "2.0.0-rc.0", features = ["postgres"] }
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.23"
|
||||||
|
|
||||||
@ -18,6 +24,10 @@ chrono = "0.4.23"
|
|||||||
rocket = "0.5.0-rc.2"
|
rocket = "0.5.0-rc.2"
|
||||||
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "c17e814" }
|
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "c17e814" }
|
||||||
|
|
||||||
|
# GraphQL
|
||||||
|
juniper = "0.15.10"
|
||||||
|
juniper_rocket = "0.8.2"
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = "0.3.16"
|
tracing-subscriber = "0.3.16"
|
@ -1,2 +1,73 @@
|
|||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
|
||||||
|
use self::models::languages::Language;
|
||||||
|
use diesel::pg::PgConnection;
|
||||||
|
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
|
||||||
|
use dotenvy::dotenv;
|
||||||
|
use std::env;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
// use diesel::query_dsl::RunQueryDsl;
|
||||||
|
|
||||||
|
pub struct Database {
|
||||||
|
conn: Pool<ConnectionManager<PgConnection>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl juniper::Context for Database {}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
conn: Database::get_connection_pool(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>>, ()> {
|
||||||
|
self.conn
|
||||||
|
.get()
|
||||||
|
.map_err(|e| info!("Failed to connect to database: {:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_languages(&self) -> Result<Vec<Language>, ()> {
|
||||||
|
use self::schema::languages::dsl::languages;
|
||||||
|
languages.load::<Language>(&mut self.conn()?).map_err(|e| {
|
||||||
|
info!("Failed to retrieve languages from database: {:?}", e);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language(&self, name: &str) -> Option<Language> {
|
||||||
|
use self::schema::languages::dsl::languages;
|
||||||
|
match &mut self.conn() {
|
||||||
|
Ok(val) => languages
|
||||||
|
.find(name.to_string())
|
||||||
|
.first::<Language>(val)
|
||||||
|
.map_or_else(
|
||||||
|
|e| {
|
||||||
|
info!(
|
||||||
|
"Failed to retrieve language {} from database: {:?}",
|
||||||
|
name, e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Some,
|
||||||
|
),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
use super::super::schema::{langandagents, languages};
|
use super::super::schema::{langandagents, languages};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use juniper::GraphQLEnum;
|
||||||
|
|
||||||
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq)]
|
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq, GraphQLEnum)]
|
||||||
#[DieselTypePath = "crate::db::schema::sql_types::Release"]
|
#[DieselTypePath = "crate::db::schema::sql_types::Release"]
|
||||||
pub enum Release {
|
pub enum Release {
|
||||||
Public,
|
Public,
|
||||||
|
#[graphql(name="NON_COMMERCIAL")]
|
||||||
NonCommercial,
|
NonCommercial,
|
||||||
Research,
|
Research,
|
||||||
Private,
|
Private,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq)]
|
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq, GraphQLEnum)]
|
||||||
#[DieselTypePath = "crate::db::schema::sql_types::Dictgenre"]
|
#[DieselTypePath = "crate::db::schema::sql_types::Dictgenre"]
|
||||||
pub enum DictGenre {
|
pub enum DictGenre {
|
||||||
General,
|
General,
|
||||||
@ -22,7 +24,7 @@ pub enum DictGenre {
|
|||||||
Terminology,
|
Terminology,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq)]
|
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq, Eq, GraphQLEnum)]
|
||||||
#[DieselTypePath = "crate::db::schema::sql_types::Agentlanguagerelation"]
|
#[DieselTypePath = "crate::db::schema::sql_types::Agentlanguagerelation"]
|
||||||
pub enum AgentLanguageRelation {
|
pub enum AgentLanguageRelation {
|
||||||
Publisher,
|
Publisher,
|
||||||
@ -31,17 +33,35 @@ pub enum AgentLanguageRelation {
|
|||||||
|
|
||||||
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
release: Release,
|
|
||||||
created: chrono::NaiveDateTime,
|
|
||||||
name: String,
|
name: String,
|
||||||
owner: String,
|
|
||||||
targetlanguage: Vec<String>,
|
|
||||||
genre: Vec<DictGenre>,
|
|
||||||
native: Option<String>,
|
native: Option<String>,
|
||||||
|
release: Release,
|
||||||
|
targetlanguage: Vec<Option<String>>,
|
||||||
|
genre: Vec<Option<DictGenre>>,
|
||||||
abstract_: Option<String>,
|
abstract_: Option<String>,
|
||||||
|
created: chrono::NaiveDateTime,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
rights: Option<String>,
|
rights: Option<String>,
|
||||||
license: Option<String>,
|
license: Option<String>,
|
||||||
|
owner: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Language {
|
||||||
|
#[graphql(name = "release")]
|
||||||
|
fn release(&self) -> Release {
|
||||||
|
self.release.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(name = "created")]
|
||||||
|
fn created(&self) -> String {
|
||||||
|
self.created.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(name = "name")]
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -7,6 +7,17 @@ pub struct User {
|
|||||||
pub username: String,
|
pub username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl User {
|
||||||
|
pub fn id(&self) -> &str {
|
||||||
|
self.id.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn username(&self) -> &str {
|
||||||
|
self.username.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
||||||
#[diesel(table_name = userfollows)]
|
#[diesel(table_name = userfollows)]
|
||||||
pub struct UserFollow {
|
pub struct UserFollow {
|
||||||
|
@ -31,7 +31,7 @@ pub enum PartOfSpeech {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
||||||
struct Word {
|
pub struct Word {
|
||||||
norm: String,
|
norm: String,
|
||||||
native: Option<String>,
|
native: Option<String>,
|
||||||
lemma: Option<String>,
|
lemma: Option<String>,
|
||||||
@ -48,7 +48,7 @@ struct Word {
|
|||||||
|
|
||||||
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
#[derive(Queryable, Insertable, Debug, Clone, PartialEq, Eq)]
|
||||||
#[diesel(table_name = wordrelation)]
|
#[diesel(table_name = wordrelation)]
|
||||||
struct WordRelation {
|
pub struct WordRelation {
|
||||||
id: i32,
|
id: i32,
|
||||||
wordsource: String,
|
wordsource: String,
|
||||||
wordtarget: String,
|
wordtarget: String,
|
||||||
|
63
src/graphql.rs
Normal file
63
src/graphql.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use rocket::response::content::RawHtml;
|
||||||
|
use rocket::State;
|
||||||
|
|
||||||
|
use juniper::EmptySubscription;
|
||||||
|
use juniper_rocket::{GraphQLRequest, GraphQLResponse};
|
||||||
|
|
||||||
|
use crate::db::models::languages::Language;
|
||||||
|
use crate::db::Database;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Query;
|
||||||
|
|
||||||
|
#[juniper::graphql_object(Context = Database)]
|
||||||
|
impl Query {
|
||||||
|
#[graphql(name = "allLanguages")]
|
||||||
|
fn all_languages(context: &Database) -> Vec<Language> {
|
||||||
|
context.all_languages().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn language(context: &Database, name: String) -> Option<Language> {
|
||||||
|
context.language(name.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)
|
||||||
|
}
|
22
src/main.rs
22
src/main.rs
@ -1,6 +1,7 @@
|
|||||||
#![warn(clippy::style, clippy::pedantic)]
|
#![warn(clippy::style, clippy::pedantic)]
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
|
mod graphql;
|
||||||
|
|
||||||
use std::{env, error::Error};
|
use std::{env, error::Error};
|
||||||
|
|
||||||
@ -44,6 +45,10 @@ fn make_cors() -> Result<rocket_cors::Cors, rocket_cors::Error> {
|
|||||||
|
|
||||||
#[rocket::main]
|
#[rocket::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
use graphql::{
|
||||||
|
create_schema, get_graphql_handler, graphiql, post_graphql_handler,
|
||||||
|
};
|
||||||
|
|
||||||
setup_logging();
|
setup_logging();
|
||||||
|
|
||||||
info!("Reading environment variables");
|
info!("Reading environment variables");
|
||||||
@ -52,7 +57,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let cors = make_cors()?;
|
let cors = make_cors()?;
|
||||||
debug!("CORS: {:?}", cors);
|
debug!("CORS: {:?}", cors);
|
||||||
|
|
||||||
#[allow(clippy::let_underscore_drop)]
|
#[allow(clippy::let_underscore_drop, clippy::no_effect_underscore_binding)]
|
||||||
let _ = rocket::build().attach(cors).launch().await?;
|
let _ = rocket::build()
|
||||||
|
.attach(cors)
|
||||||
|
.manage(db::Database::new())
|
||||||
|
.manage(create_schema())
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
rocket::routes![
|
||||||
|
graphiql,
|
||||||
|
get_graphql_handler,
|
||||||
|
post_graphql_handler
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.launch()
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user