Check pointing some exploration with graphql for the reldata service

This commit is contained in:
R Tyler Croy 2021-03-03 15:16:54 -08:00
parent 08d584974b
commit 14d78e009f
5 changed files with 116 additions and 4 deletions

2
.gitignore vendored
View File

@ -8,4 +8,4 @@ build/
*.tar.gz *.tar.gz
tmp* tmp*
.hypothesis/ .hypothesis/
otto.db otto.db*

10
Cargo.lock generated
View File

@ -1677,6 +1677,15 @@ dependencies = [
"waker-fn", "waker-fn",
] ]
[[package]]
name = "itertools"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.7" version = "0.4.7"
@ -2183,6 +2192,7 @@ dependencies = [
"pretty_env_logger 0.4.0", "pretty_env_logger 0.4.0",
"sqlx", "sqlx",
"tide", "tide",
"uuid",
] ]
[[package]] [[package]]

View File

@ -5,7 +5,7 @@
CREATE TABLE IF NOT EXISTS projects CREATE TABLE IF NOT EXISTS projects
( (
uuid BLOB PRIMARY KEY NOT NULL, uuid TEXT PRIMARY KEY NOT NULL,
path TEXT UNIQUE NOT NULL, path TEXT UNIQUE NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
description TEXT, description TEXT,

View File

@ -5,7 +5,7 @@ authors = ["R. Tyler Croy <rtyler@brokenco.de>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
async-graphql = "2.0" async-graphql = { version = "2.0", features = ["chrono", "dataloader", "log", "uuid"] }
async-graphql-tide = "2.0" async-graphql-tide = "2.0"
async-trait = "0.1" async-trait = "0.1"
async-std = { version = "1", features = ["attributes"]} async-std = { version = "1", features = ["attributes"]}
@ -16,3 +16,4 @@ otto-models = { path = "../../crates/models" }
pretty_env_logger = "~0.4.0" pretty_env_logger = "~0.4.0"
sqlx = { version = "~0.5.1", features = ["runtime-async-std-rustls", "postgres", "tls", "json", "sqlite", "chrono", "macros", "uuid"]} sqlx = { version = "~0.5.1", features = ["runtime-async-std-rustls", "postgres", "tls", "json", "sqlite", "chrono", "macros", "uuid"]}
tide = "0.16" tide = "0.16"
uuid = { version = "0.8", features = ["serde", "v4"]}

View File

@ -1,8 +1,34 @@
/* /*
* The relational data service largely is meant to expose information from an underlying database * The relational data service largely is meant to expose information from an underlying database
*/ */
use dotenv::dotenv;
use log::*;
use tide::Request; use tide::Request;
use async_graphql::dataloader::{DataLoader, Loader};
use async_graphql::futures_util::TryStreamExt;
use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
use async_graphql::{
Context, EmptyMutation, EmptySubscription, FieldError, Object, Result, Schema, SimpleObject,
};
use async_trait::async_trait;
use sqlx::{Pool, SqlitePool};
use std::collections::HashMap;
use tide::{http::mime, Body, Response, StatusCode};
use uuid::Uuid;
/**
* QueryState is a simple struct to pass data through to async-graphql implementations
*/
struct QueryState {
pool: SqlitePool,
data_loader: DataLoader<ProjectLoader>,
}
/**
* Simple/empty healthcheck endpoint which can be used to determine whether the webservice is at
* least minimally functional
*/
async fn healthcheck(_req: Request<()>) -> tide::Result { async fn healthcheck(_req: Request<()>) -> tide::Result {
Ok(tide::Response::builder(200) Ok(tide::Response::builder(200)
.body("{}") .body("{}")
@ -10,13 +36,38 @@ async fn healthcheck(_req: Request<()>) -> tide::Result {
.build()) .build())
} }
/**
* Main web service set up
*/
#[async_std::main] #[async_std::main]
async fn main() -> std::io::Result<()> { async fn main() -> async_graphql::Result<()> {
use std::{env, net::TcpListener, os::unix::io::FromRawFd}; use std::{env, net::TcpListener, os::unix::io::FromRawFd};
pretty_env_logger::init(); pretty_env_logger::init();
dotenv().ok();
let pool: SqlitePool = Pool::connect(&env::var("DATABASE_URL")?).await?;
debug!("Connecting to: {}", env::var("DATABASE_URL")?);
let qs = QueryState {
pool: pool.clone(),
data_loader: DataLoader::new(ProjectLoader::new(pool.clone())),
};
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(qs)
.finish();
let mut app = tide::new(); let mut app = tide::new();
app.at("/health").get(healthcheck); app.at("/health").get(healthcheck);
app.at("/graphql")
.post(async_graphql_tide::endpoint(schema));
app.at("/").get(|_| async move {
let mut resp = Response::new(StatusCode::Ok);
resp.set_body(Body::from_string(playground_source(
GraphQLPlaygroundConfig::new("/graphql"),
)));
resp.set_content_type(mime::HTML);
Ok(resp)
});
if let Some(fd) = env::var("LISTEN_FD").ok().and_then(|fd| fd.parse().ok()) { if let Some(fd) = env::var("LISTEN_FD").ok().and_then(|fd| fd.parse().ok()) {
app.listen(unsafe { TcpListener::from_raw_fd(fd) }).await?; app.listen(unsafe { TcpListener::from_raw_fd(fd) }).await?;
@ -26,5 +77,55 @@ async fn main() -> std::io::Result<()> {
Ok(()) Ok(())
} }
#[derive(sqlx::FromRow, Clone, Debug, SimpleObject)]
pub struct Project {
uuid: Uuid,
path: String,
title: String,
}
pub struct ProjectLoader(SqlitePool);
impl ProjectLoader {
fn new(pool: SqlitePool) -> Self {
Self(pool)
}
}
#[async_trait]
impl Loader<Uuid> for ProjectLoader {
type Value = Project;
type Error = FieldError;
async fn load(&self, keys: &[Uuid]) -> Result<HashMap<Uuid, Self::Value>, Self::Error> {
let uuids = keys.iter().map(|u| u.to_string()).collect::<Vec<String>>();
Ok(
sqlx::query_as::<sqlx::Sqlite, Self::Value>("SELECT * FROM projects WHERE uuid IN (?)")
.bind(&uuids.join(","))
.fetch(&self.0)
.map_ok(|p: Project| (p.uuid, p))
.try_collect()
.await?,
)
}
}
struct QueryRoot;
#[Object]
impl QueryRoot {
async fn project(&self, ctx: &Context<'_>, id: Uuid) -> Result<Option<Project>> {
Ok(ctx
.data_unchecked::<QueryState>()
.data_loader
.load_one(id)
.await?)
}
}
#[derive(Clone)]
struct AppState {
schema: Schema<QueryRoot, EmptyMutation, EmptySubscription>,
}
#[cfg(test)] #[cfg(test)]
mod tests {} mod tests {}