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
|
||||
tmp*
|
||||
.hypothesis/
|
||||
otto.db
|
||||
otto.db*
|
||||
|
|
|
@ -1677,6 +1677,15 @@ dependencies = [
|
|||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
|
@ -2183,6 +2192,7 @@ dependencies = [
|
|||
"pretty_env_logger 0.4.0",
|
||||
"sqlx",
|
||||
"tide",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
CREATE TABLE IF NOT EXISTS projects
|
||||
(
|
||||
uuid BLOB PRIMARY KEY NOT NULL,
|
||||
uuid TEXT PRIMARY KEY NOT NULL,
|
||||
path TEXT UNIQUE NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
|
|
|
@ -5,7 +5,7 @@ authors = ["R. Tyler Croy <rtyler@brokenco.de>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
async-graphql = "2.0"
|
||||
async-graphql = { version = "2.0", features = ["chrono", "dataloader", "log", "uuid"] }
|
||||
async-graphql-tide = "2.0"
|
||||
async-trait = "0.1"
|
||||
async-std = { version = "1", features = ["attributes"]}
|
||||
|
@ -16,3 +16,4 @@ otto-models = { path = "../../crates/models" }
|
|||
pretty_env_logger = "~0.4.0"
|
||||
sqlx = { version = "~0.5.1", features = ["runtime-async-std-rustls", "postgres", "tls", "json", "sqlite", "chrono", "macros", "uuid"]}
|
||||
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
|
||||
*/
|
||||
use dotenv::dotenv;
|
||||
use log::*;
|
||||
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 {
|
||||
Ok(tide::Response::builder(200)
|
||||
.body("{}")
|
||||
|
@ -10,13 +36,38 @@ async fn healthcheck(_req: Request<()>) -> tide::Result {
|
|||
.build())
|
||||
}
|
||||
|
||||
/**
|
||||
* Main web service set up
|
||||
*/
|
||||
#[async_std::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
async fn main() -> async_graphql::Result<()> {
|
||||
use std::{env, net::TcpListener, os::unix::io::FromRawFd};
|
||||
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();
|
||||
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()) {
|
||||
app.listen(unsafe { TcpListener::from_raw_fd(fd) }).await?;
|
||||
|
@ -26,5 +77,55 @@ async fn main() -> std::io::Result<()> {
|
|||
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)]
|
||||
mod tests {}
|
||||
|
|
Loading…
Reference in New Issue