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