Check pointing some exploration with graphql for the reldata service
This commit is contained in:
parent
08d584974b
commit
14d78e009f
|
@ -8,4 +8,4 @@ build/
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
tmp*
|
tmp*
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
otto.db
|
otto.db*
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"]}
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
Loading…
Reference in New Issue