From f0c584ffbc5131b92caa3a09dd4460cbb94d61e6 Mon Sep 17 00:00:00 2001 From: James H <32926722+jayy-lmao@users.noreply.github.com> Date: Thu, 23 Jan 2020 20:52:17 +1100 Subject: [PATCH] :sparkles: introducing... graphql! (#2) * :sparkles: multi-id queries & id splitting! Nextup: Queries * :sparkles: optional query params * :alembic: persons query now exists * :alembic: cults query now exists, and people and cults exist within each other * :recycle: shimmied the bits around --- api/Cargo.toml | 10 ++++-- api/src/main.rs | 71 ++++++++++++++++++++++--------------- api/src/models/person.rs | 45 ++++++++++++++++++----- api/src/resolvers/cult.rs | 28 ++++++++------- api/src/resolvers/person.rs | 39 ++++++++++---------- api/src/schema.rs | 29 +++++++++++++++ docker-compose.yml | 1 + 7 files changed, 153 insertions(+), 70 deletions(-) create mode 100644 api/src/schema.rs diff --git a/api/Cargo.toml b/api/Cargo.toml index f326207..e1cabcd 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -7,9 +7,15 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -postgres = "0.15.2" actix-web = "1.0.7" -serde = { version = "1.0.101", features = ["derive"] } +dotenv = "0.9.0" +env_logger = "0.6" +futures = "0.1" +juniper = "0.13.1" +postgres = "0.15.2" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" [[bin]] name = "readspacer-api" diff --git a/api/src/main.rs b/api/src/main.rs index 3626a7e..960f382 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -1,39 +1,52 @@ -use actix_web::{web, App, HttpServer, Responder}; +#[macro_use] +extern crate juniper; +mod db; +mod schema; mod models; mod resolvers; mod types; -mod db; -fn greet() -> impl Responder { - "Yo there" +use actix_web::{web, App, Error, HttpResponse, HttpServer}; +use futures::future::Future; +use juniper::http::GraphQLRequest; +use juniper::http::playground::playground_source; +use std::io; +use std::sync::Arc; +use crate::schema::{create_schema, Schema}; + +fn graphql( + st: web::Data>, + data: web::Json, +) -> impl Future { + web::block(move || { + let res = data.execute(&st, &()); + Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?) + }) + .map_err(Error::from) + .and_then(|user| { + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(user)) + }) +} + +fn playground() -> HttpResponse { + let html = playground_source("http://localhost:8000/graphql"); + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html) } -fn main() { - println!("Server starting..."); - HttpServer::new(|| { - App::new() - .service(web::resource("/hello").route(web::get().to(greet))) - .service( - web::resource("/some_persons").route(web::get().to(resolvers::person::get_some_person)), - ) - .service( - web::resource("/persons").route(web::get().to(resolvers::person::get_person_list)), - ) - .service( - web::resource("/persons/{person_id}") - .route(web::get().to(resolvers::person::get_person)), - ) - // .route(web::post().to(create_person))) - // .route(web::delete().to(delete_person)) - // .route(web::put().to(update_person))) - .service( - web::resource("/cults/{cult_id}").route(web::get().to(resolvers::cult::get_cult)), - ) - .service(web::resource("/cults").route(web::get().to(resolvers::cult::get_cult_list))) +fn main() -> io::Result<()> { + let schema = std::sync::Arc::new(create_schema()); + HttpServer::new(move || { + App::new().data(schema.clone()).service( + web::resource("/graphql") + .route(web::post().to_async(graphql)) + .route(web::get().to(playground)), + ) }) - .bind("0.0.0.0:8000") - .unwrap() + .bind("0.0.0.0:8000")? .run() - .unwrap(); } diff --git a/api/src/models/person.rs b/api/src/models/person.rs index 1d41d8d..75b21af 100644 --- a/api/src/models/person.rs +++ b/api/src/models/person.rs @@ -1,11 +1,11 @@ extern crate postgres; -use crate::db::get_db_conn; use crate::types::Person; +use crate::db::get_db_conn; pub fn get_person_all(vec: &mut Vec) { let conn = get_db_conn(); for row in &conn - .query("SELECT person_id, person_name FROM persons", &[]) + .query("SELECT person_id, person_name, cult FROM persons", &[]) .unwrap() { let person = Person { @@ -19,19 +19,28 @@ pub fn get_person_all(vec: &mut Vec) { pub fn get_person_by_ids(vec: &mut Vec, ids: Vec) { //TODO: In desperate need of refactor, or else ye shall face the n+1 problem!! - - for id in ids.iter() { - get_person_by_id(vec, *id); - }; + let conn = get_db_conn(); + for row in &conn + .query( + "SELECT person_id, person_name, cult FROM persons WHERE person_id = ANY($1)", + &[&ids], + ) + .unwrap() + { + let person = Person { + person_id: row.get(0), + person_name: row.get(1), + cult: row.get(2), + }; + vec.push(person); + } } pub fn get_person_by_id(vec: &mut Vec, id: i32) { - // let pg_connection_string = env::var("DATABASE_URI").expect("need a db uri"); - // let conn = Connection::connect(&pg_connection_string[..], TlsMode::None).unwrap(); let conn = get_db_conn(); for row in &conn .query( - "SELECT person_id, person_name, cult FROM persons WHERE person_id = $1", + "select person_id, person_name, cult from persons where person_id = $1", &[&id], ) .unwrap() @@ -44,3 +53,21 @@ pub fn get_person_by_id(vec: &mut Vec, id: i32) { vec.push(person); } } + +pub fn get_person_by_cult(vec: &mut Vec, cult: i32) { + let conn = get_db_conn(); + for row in &conn + .query( + "select person_id, person_name, cult from persons where cult = $1", + &[&cult], + ) + .unwrap() + { + let person = Person { + person_id: row.get(0), + person_name: row.get(1), + cult: row.get(2), + }; + vec.push(person); + } +} diff --git a/api/src/resolvers/cult.rs b/api/src/resolvers/cult.rs index 0912aa1..221de4f 100644 --- a/api/src/resolvers/cult.rs +++ b/api/src/resolvers/cult.rs @@ -1,15 +1,19 @@ -use actix_web::{web, Responder}; +use crate::types::{Cult, Person}; +use crate::models::person:: get_person_by_cult; -use crate::{models, types::Cult}; +#[juniper::object(description = "A real human CULT")] +impl Cult { + pub fn id(&self) -> i32 { + self.id + } -pub fn get_cult_list() -> impl Responder { - let mut vec: Vec = Vec::new(); - models::cult::get_cult_all(&mut vec); - web::Json(vec) -} + pub fn name(&self) -> &str { + self.name.as_str() + } -pub fn get_cult(info: web::Path) -> impl Responder { - let mut vec: Vec = Vec::new(); - models::cult::get_cult_by_id(&mut vec, info.into_inner()); - web::Json(vec) -} + pub fn members(&self) -> Vec { + let mut vec: Vec = Vec::new(); + get_person_by_cult(&mut vec, self.id); + vec + } +} \ No newline at end of file diff --git a/api/src/resolvers/person.rs b/api/src/resolvers/person.rs index 9e53e40..6224cce 100644 --- a/api/src/resolvers/person.rs +++ b/api/src/resolvers/person.rs @@ -1,21 +1,24 @@ -use actix_web::{web, Responder}; +use crate::types::{Cult, Person}; +use crate::models::{ + cult::get_cult_by_id, +}; -use crate::{models, types::Person}; +#[juniper::object(description = "A real human bean")] +impl Person { + pub fn id(&self) -> i32 { + self.person_id + } -pub fn get_person_list() -> impl Responder { - let mut vec: Vec = Vec::new(); - models::person::get_person_all(&mut vec); - web::Json(vec) -} + pub fn name(&self) -> &str { + self.person_name.as_str() + } -pub fn get_some_person() -> impl Responder { - let mut vec: Vec = Vec::new(); - models::person::get_person_by_ids(&mut vec, vec![1,2,3]); - web::Json(vec) -} - -pub fn get_person(info: web::Path) -> impl Responder { - let mut vec: Vec = Vec::new(); - models::person::get_person_by_id(&mut vec, info.into_inner()); - web::Json(vec) -} + pub fn cult(&self) -> Vec { + let mut vec: Vec = Vec::new(); + match self.cult { + Some(cult_id) => get_cult_by_id(&mut vec, cult_id), + None => (), + } + vec + } +} \ No newline at end of file diff --git a/api/src/schema.rs b/api/src/schema.rs new file mode 100644 index 0000000..d6785a6 --- /dev/null +++ b/api/src/schema.rs @@ -0,0 +1,29 @@ +use juniper::{EmptyMutation, RootNode}; + +use crate::models::{ + cult::get_cult_all, + person::get_person_all, +}; +use crate::types::{Cult, Person}; +pub struct QueryRoot; + +#[juniper::object] +impl QueryRoot { + fn persons() -> Vec { + let mut vec: Vec = Vec::new(); + get_person_all(&mut vec); + vec + } + + fn cults() -> Vec { + let mut vec: Vec = Vec::new(); + get_cult_all(&mut vec); + vec + } +} + +pub type Schema = RootNode<'static, QueryRoot, EmptyMutation<()>>; + +pub fn create_schema() -> Schema { + Schema::new(QueryRoot {}, EmptyMutation::new()) +} diff --git a/docker-compose.yml b/docker-compose.yml index 401488a..17845e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: image: postgres:12 mem_limit: 1536MB mem_reservation: 1G + command: ["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"] environment: POSTGRES_USER: jayylmao POSTGRES_PASSWORD: yeetus