1use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
2use async_graphql::{ObjectType, SubscriptionType};
3use async_graphql_warp::{GraphQLBadRequest, GraphQLResponse};
4
5use dotenv::dotenv;
6use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
7use std::convert::Infallible;
8use std::str::FromStr;
9use warp::{http::Response as HttpResponse, http::StatusCode, Filter, Rejection};
10
11pub use anyhow;
12pub use async_graphql::{
13 self, Context, EmptyMutation, EmptySubscription, Enum, InputObject, Interface, Object, Result,
14 Scalar, Schema, SchemaBuilder, SimpleObject, Union,
15};
16pub use chrono;
17pub use serde::{self, Deserialize, Serialize};
18pub use serde_json;
19pub use sqlx::{self, sqlite::SqlitePool};
20pub use tokio;
21pub use uuid::{self, Uuid};
22
23pub struct SimpleGraphql<
25 Q: ObjectType + 'static,
26 M: ObjectType + 'static,
27 S: SubscriptionType + 'static,
28> {
29 builder: SchemaBuilder<Q, M, S>,
30 sqlite: Option<SimpleSqlite>,
31 spa: Option<SpaConfig>,
32}
33
34pub struct SpaConfig {
35 assets: String,
36 index: String,
37}
38
39impl<Q: ObjectType + 'static, M: ObjectType + 'static, S: SubscriptionType + 'static>
40 SimpleGraphql<Q, M, S>
41{
42 pub fn new(builder: SchemaBuilder<Q, M, S>) -> Self {
43 Self {
44 builder,
45 sqlite: None,
46 spa: None,
47 }
48 }
49
50 pub fn with_sqlite(self, sqlite: SimpleSqlite) -> Self {
51 Self {
52 sqlite: Some(sqlite),
53 ..self
54 }
55 }
56
57 pub fn with_spa(self, assets_dir: &str, index_file: &str) -> Self {
58 Self {
59 spa: Some(SpaConfig {
60 assets: assets_dir.to_string(),
61 index: index_file.to_string(),
62 }),
63 ..self
64 }
65 }
66
67 pub async fn run(self) {
68 dotenv().ok();
69
70 let schema = if let Some(sqlite) = self.sqlite {
71 self.builder.data(sqlite.pool()).finish()
72 } else {
73 self.builder.finish()
74 };
75
76 let graphql_post = warp::path!("graphql")
77 .and(async_graphql_warp::graphql(schema))
78 .and_then(
79 |(schema, request): (Schema<Q, M, S>, async_graphql::Request)| async move {
80 Ok::<_, Infallible>(GraphQLResponse::from(schema.execute(request).await))
81 },
82 );
83
84 let graphql_playground = warp::path!("playground").and(warp::get()).map(|| {
85 HttpResponse::builder()
86 .header("content-type", "text/html")
87 .body(playground_source(GraphQLPlaygroundConfig::new("/graphql")))
88 });
89
90 let recover = |err: Rejection| async move {
91 if let Some(GraphQLBadRequest(err)) = err.find() {
92 return Ok::<_, Infallible>(warp::reply::with_status(
93 err.to_string(),
94 StatusCode::BAD_REQUEST,
95 ));
96 }
97
98 Ok(warp::reply::with_status(
99 "INTERNAL_SERVER_ERROR".to_string(),
100 StatusCode::INTERNAL_SERVER_ERROR,
101 ))
102 };
103
104 if let Some(spa) = self.spa {
106 let assets = warp::fs::dir(spa.assets);
107 let spa_index = warp::fs::file(spa.index);
108
109 let routes = graphql_playground
110 .or(graphql_post)
111 .or(assets)
112 .or(spa_index)
113 .recover(recover);
114 print_start();
115 warp::serve(routes).run(([0, 0, 0, 0], 3030)).await;
116 } else {
117 let routes = graphql_playground.or(graphql_post).recover(recover);
118 print_start();
119 warp::serve(routes).run(([0, 0, 0, 0], 3030)).await;
120 }
121 }
122}
123
124fn print_start() {
125 println!("Running at http://0.0.0.0:3030");
126 println!("\nRoutes:");
127 println!("\t/graphql");
128 println!("\t/playground");
129}
130
131pub struct SimpleSqlite {
133 pool: SqlitePool,
134}
135
136impl SimpleSqlite {
137 pub fn new(filename: &str) -> Self {
138 let url = format!("sqlite://{}", filename);
139 let options = SqliteConnectOptions::from_str(&url)
140 .unwrap()
141 .create_if_missing(true);
142 let pool = SqlitePoolOptions::new().connect_lazy_with(options);
143
144 Self { pool }
145 }
146
147 pub fn pool(&self) -> SqlitePool {
148 self.pool.clone()
149 }
150}