First Commit
This commit is contained in:
commit
116973d5ae
|
@ -0,0 +1 @@
|
|||
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "axum-juniper"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.37.0", features = [
|
||||
"macros",
|
||||
"net",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
] }
|
||||
tokio-stream = "0.1.15"
|
||||
r2d2 = { version = "0.8.10" }
|
||||
juniper = { version = "0.16.1" }
|
||||
axum = { version = "0.7.5", features = ["json", "macros", "tokio", "tracing"] }
|
||||
juniper_axum = { version = "0.1.0", features = ["subscriptions"] }
|
||||
diesel = { version = "2.1.6", features = ["postgres", "r2d2"] }
|
||||
dotenvy = "0.15.7"
|
||||
serde = "1.0.202"
|
||||
serde_json = "1.0.117"
|
||||
futures = "0.3.30"
|
||||
anyhow = "1.0.86"
|
||||
juniper_graphql_ws = { version = "0.4.0", features = ["graphql-transport-ws"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
|
||||
[dev-dependencies]
|
|
@ -0,0 +1,9 @@
|
|||
# For documentation on how to configure this file,
|
||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
||||
custom_type_derives = ["diesel::query_builder::QueryId"]
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations"
|
|
@ -0,0 +1,6 @@
|
|||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
|
@ -0,0 +1,36 @@
|
|||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
|
||||
|
||||
|
||||
-- Sets up a trigger for the given table to automatically set a column called
|
||||
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||
-- in the modified columns)
|
||||
--
|
||||
-- # Example
|
||||
--
|
||||
-- ```sql
|
||||
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||
--
|
||||
-- SELECT diesel_manage_updated_at('users');
|
||||
-- ```
|
||||
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF (
|
||||
NEW IS DISTINCT FROM OLD AND
|
||||
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||
) THEN
|
||||
NEW.updated_at := current_timestamp;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE typen;-- This file should undo anything in `up.sql`
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE typen (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE hersteller;
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE hersteller (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE modelle;
|
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE modelle (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL,
|
||||
typ_id SERIAL,
|
||||
hersteller_id SERIAL,
|
||||
CONSTRAINT fk_typ FOREIGN KEY(typ_id) REFERENCES typen(id),
|
||||
CONSTRAINT fk_hersteller FOREIGN KEY (hersteller_id) REFERENCES hersteller(id)
|
||||
)
|
|
@ -0,0 +1,39 @@
|
|||
use diesel::r2d2::{ConnectionManager, Pool, PoolError};
|
||||
use diesel::PgConnection;
|
||||
use dotenvy::dotenv;
|
||||
use std::env;
|
||||
|
||||
pub type PgPool = Pool<ConnectionManager<PgConnection>>;
|
||||
|
||||
fn init_pool(database_url: &str) -> Result<PgPool, PoolError> {
|
||||
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
||||
Pool::builder().build(manager)
|
||||
}
|
||||
|
||||
fn establish_connection() -> PgPool {
|
||||
dotenv().ok();
|
||||
|
||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
init_pool(&database_url).unwrap_or_else(|_| panic!("Could not create database pool"))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Database {
|
||||
pub pool: PgPool,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn new() -> Database {
|
||||
Database {
|
||||
pool: establish_connection(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Database {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
|
@ -0,0 +1 @@
|
|||
pub mod typ_dao;
|
|
@ -0,0 +1,28 @@
|
|||
use crate::{database::Database, models::typ::Typ};
|
||||
use anyhow::{Ok, Result};
|
||||
use diesel::{QueryDsl, RunQueryDsl, SelectableHelper};
|
||||
|
||||
pub struct TypDAO {}
|
||||
|
||||
impl TypDAO {
|
||||
pub fn get_all_typen(context: &Database) -> Result<Vec<Typ>> {
|
||||
use crate::schema::typen::dsl::*;
|
||||
let connection = &mut context.pool.get()?;
|
||||
|
||||
let alle_typen = typen.load::<Typ>(connection)?;
|
||||
|
||||
Ok(alle_typen)
|
||||
}
|
||||
|
||||
pub fn get_typ_by_id(context: &Database, typ_id: i32) -> Result<Typ> {
|
||||
use crate::schema::typen::dsl::*;
|
||||
let connection = &mut context.pool.get()?;
|
||||
|
||||
let typ = typen
|
||||
.find(typ_id)
|
||||
.select(Typ::as_select())
|
||||
.first(connection)?;
|
||||
|
||||
Ok(typ)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
mod database;
|
||||
mod diesel_dao;
|
||||
mod models;
|
||||
mod mutation;
|
||||
mod query;
|
||||
mod schema;
|
||||
mod subscription;
|
||||
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
response::Html,
|
||||
routing::{get, on, MethodFilter},
|
||||
Extension, Router,
|
||||
};
|
||||
use juniper::RootNode;
|
||||
use juniper_axum::{graphiql, graphql, playground, subscriptions};
|
||||
use juniper_graphql_ws::ConnectionConfig;
|
||||
use mutation::Mutation;
|
||||
use query::Query;
|
||||
use subscription::Subscription;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::database::Database;
|
||||
|
||||
type Schema = RootNode<'static, Query, Mutation, Subscription>;
|
||||
|
||||
async fn homepage() -> Html<&'static str> {
|
||||
"<html><h1>juniper_axum/simple example</h1>\
|
||||
<div>visit <a href=\"/graphiql\">GraphiQL</a></div>\
|
||||
<div>visit <a href=\"/playground\">GraphQL Playground</a></div>\
|
||||
</html>"
|
||||
.into()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::INFO)
|
||||
.init();
|
||||
|
||||
let database = Database::new();
|
||||
let schema = Schema::new(Query, Mutation, Subscription);
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/graphql",
|
||||
on(
|
||||
MethodFilter::GET.or(MethodFilter::POST),
|
||||
graphql::<Arc<Schema>>,
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/subscriptions",
|
||||
get(subscriptions::ws::<Arc<Schema>>(ConnectionConfig::new(
|
||||
database,
|
||||
))),
|
||||
)
|
||||
.route("/graphiql", get(graphiql("/graphql", "/subscriptions")))
|
||||
.route("/playground", get(playground("/graphql", "/subscriptions")))
|
||||
.route("/", get(homepage))
|
||||
.layer(Extension(Arc::new(schema)));
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
let listener = TcpListener::bind(addr)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("failed to listen on {addr}: {e}"));
|
||||
|
||||
tracing::info!("listening on {addr}");
|
||||
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("failed to run `axum::serve`: {e}"));
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
use diesel::Queryable;
|
||||
|
||||
#[derive(Queryable, Debug)]
|
||||
#[diesel(table_name = hersteller)]
|
||||
pub struct Hersteller {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
pub mod hersteller;
|
||||
pub mod modell;
|
||||
pub mod typ;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
use diesel::Queryable;
|
||||
|
||||
#[derive(Queryable, Debug)]
|
||||
#[diesel(table_name = modelle)]
|
||||
pub struct Modell {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub typ_id: i32,
|
||||
pub hersteller_id: i32,
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use diesel::{associations::Identifiable, prelude::Insertable, Queryable, Selectable};
|
||||
use juniper::{GraphQLInputObject, GraphQLObject};
|
||||
|
||||
use crate::schema::typen;
|
||||
|
||||
#[derive(GraphQLObject, Queryable, Selectable, Identifiable)]
|
||||
#[diesel(table_name = typen)]
|
||||
pub struct Typ {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLInputObject, Insertable)]
|
||||
#[diesel(table_name = typen)]
|
||||
pub struct TypInput {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
pub struct TypResponse {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
use crate::{
|
||||
database::Database,
|
||||
models::typ::{Typ, TypInput},
|
||||
schema::typen,
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
use juniper::{graphql_object, FieldResult};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Mutation;
|
||||
|
||||
#[graphql_object(context = Database)]
|
||||
impl Mutation {
|
||||
fn new_typ(#[graphql(context)] database: &Database, typ_input: TypInput) -> FieldResult<Typ> {
|
||||
let connection = &mut database.pool.get()?;
|
||||
|
||||
let typ = diesel::insert_into(typen::table)
|
||||
.values(&typ_input)
|
||||
.get_result::<Typ>(connection)?;
|
||||
|
||||
Ok(typ)
|
||||
}
|
||||
|
||||
fn edit_typ(
|
||||
#[graphql(context)] database: &Database,
|
||||
typ_id: i32,
|
||||
typ_name: String,
|
||||
) -> FieldResult<Typ> {
|
||||
use crate::schema::typen::dsl::*;
|
||||
let connection = &mut database.pool.get()?;
|
||||
|
||||
let update_typ = diesel::update(typen.find(typ_id))
|
||||
.set(name.eq(typ_name))
|
||||
.get_result::<Typ>(connection)?;
|
||||
|
||||
Ok(update_typ)
|
||||
}
|
||||
|
||||
fn delete_typ(#[graphql(context)] database: &Database, typ_id: i32) -> FieldResult<String> {
|
||||
use crate::schema::typen::dsl::*;
|
||||
let conn = &mut database.pool.get()?;
|
||||
|
||||
diesel::delete(typen.find(typ_id)).execute(conn)?;
|
||||
|
||||
Ok("Typ wurde erfolgreich gelöscht".to_string())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
use crate::{database::Database, diesel_dao::typ_dao::TypDAO, models::typ::Typ};
|
||||
use juniper::{graphql_object, FieldResult};
|
||||
|
||||
pub struct Query;
|
||||
|
||||
#[graphql_object(context = Database)]
|
||||
impl Query {
|
||||
/// Zeigt alle Gerätetypen an.
|
||||
fn typen(#[graphql(context)] context: &Database) -> FieldResult<Vec<Typ>> {
|
||||
let alle_typen = TypDAO::get_all_typen(context)?;
|
||||
Ok(alle_typen)
|
||||
}
|
||||
|
||||
/// Zeigt einen Gerätetypen an
|
||||
fn typ(
|
||||
#[graphql(context)] context: &Database,
|
||||
#[graphql(description = "Die ID des Typs.")] typ_id: i32,
|
||||
) -> FieldResult<Typ> {
|
||||
let typ = TypDAO::get_typ_by_id(context, typ_id)?;
|
||||
Ok(typ)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
hersteller (id) {
|
||||
id -> Int4,
|
||||
name -> Varchar,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
modelle (id) {
|
||||
id -> Int4,
|
||||
name -> Varchar,
|
||||
typ_id -> Int4,
|
||||
hersteller_id -> Int4,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
typen (id) {
|
||||
id -> Int4,
|
||||
name -> Varchar,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(modelle -> hersteller (hersteller_id));
|
||||
diesel::joinable!(modelle -> typen (typ_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
hersteller,
|
||||
modelle,
|
||||
typen,
|
||||
);
|
|
@ -0,0 +1,25 @@
|
|||
use futures::stream::{BoxStream, StreamExt as _};
|
||||
use juniper::{graphql_subscription, FieldError};
|
||||
use std::time::Duration;
|
||||
use tokio::time::interval;
|
||||
use tokio_stream::wrappers::IntervalStream;
|
||||
|
||||
use crate::database::Database;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Subscription;
|
||||
|
||||
type NumberStream = BoxStream<'static, Result<i32, FieldError>>;
|
||||
|
||||
#[graphql_subscription(context = Database)]
|
||||
impl Subscription {
|
||||
/// Counts seconds.
|
||||
async fn count() -> NumberStream {
|
||||
let mut value = 0;
|
||||
let stream = IntervalStream::new(interval(Duration::from_secs(1))).map(move |_| {
|
||||
value += 1;
|
||||
Ok(value)
|
||||
});
|
||||
Box::pin(stream)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue