This commit is contained in:
2026-05-31 20:24:10 +02:00
parent 49cd648d5b
commit a1e8d34210
27 changed files with 426 additions and 23 deletions

View File

@@ -1,3 +1,4 @@
pub mod benutzer;
pub mod gruppe;
pub mod rolle;
pub mod typ;

5
src/domain/typ.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod dataloader;
pub mod entity;
pub mod model;
pub mod repository;
pub mod service;

View File

2
src/domain/typ/entity.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod typ;
pub use typ::Typ;

View File

@@ -0,0 +1,8 @@
use crate::scalar::{Id, Time};
pub struct Typ {
pub id: Id,
pub typname: String,
pub erstellt_am: Option<Time>,
pub geaendert_am: Option<Time>,
}

9
src/domain/typ/model.rs Normal file
View File

@@ -0,0 +1,9 @@
mod typ;
mod typ_erstelle_input;
mod typ_loeschen_input;
mod typ_update_input;
pub use typ::Typ;
pub use typ_erstelle_input::TypErstelleInput;
pub use typ_loeschen_input::TypLoeschenInput;
pub use typ_update_input::TypUpdateInput;

View File

@@ -0,0 +1,22 @@
use async_graphql::{ComplexObject, SimpleObject};
use crate::scalar::{Id, Time};
#[derive(sqlx::FromRow, SimpleObject, Clone, Debug)]
#[graphql(complex)]
pub struct Typ {
/// Die UUID einer Typ
pub id: Id,
/// Der Typname
pub typname: String,
/// Wann die Typ erstellt wurde
pub erstellt_am: Time,
/// Wann die Typ geaendert wurde
pub geaendert_am: Time,
}
#[ComplexObject]
impl Typ {}

View File

@@ -0,0 +1,7 @@
use async_graphql::InputObject;
#[derive(InputObject)]
pub struct TypErstelleInput {
/// Der Name eines Gerätetyps
pub typname: String,
}

View File

@@ -0,0 +1,9 @@
use async_graphql::InputObject;
use crate::scalar::Id;
#[derive(InputObject)]
pub struct TypLoeschenInput {
/// Die ID einer Typ
pub id: Id,
}

View File

@@ -0,0 +1,11 @@
use crate::scalar::Id;
use async_graphql::InputObject;
#[derive(InputObject)]
pub struct TypUpdateInput {
/// Die ID einer Typ
pub id: Id,
/// Der Name einer Typ
pub typname: String,
}

View File

@@ -0,0 +1,19 @@
mod typ_alle;
mod typ_erstellen;
mod typ_loeschen;
mod typ_update;
#[derive(Debug, Clone)]
pub struct Repository {}
impl Repository {
pub fn new() -> Repository {
Repository {}
}
}
impl Default for Repository {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,16 @@
use anyhow::Error;
use super::Repository;
use crate::{database::Queryer, domain::typ::model};
impl Repository {
pub async fn typ_alle<'c, C: Queryer<'c>>(&self, db: C) -> Result<Vec<model::Typ>, Error> {
const QUERY: &str = r#"
SELECT * FROM typen
"#;
let typen = sqlx::query_as::<_, model::Typ>(QUERY).fetch_all(db).await?;
Ok(typen)
}
}

View File

@@ -0,0 +1,25 @@
use anyhow::Error;
use super::Repository;
use crate::{database::Queryer, domain::typ::model, scalar::Id};
impl Repository {
pub async fn typ_zeige_einen<'c, C: Queryer<'c>>(
&self,
db: C,
id: Id,
) -> Result<model::Typ, Error> {
const QUERY: &str = r#"
SELECT id, erstellt_am, geaendert_am, typname
FROM typen
WHERE id = $1
"#;
let row = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(id)
.fetch_one(db)
.await?;
Ok(row)
}
}

View File

@@ -0,0 +1,40 @@
use anyhow::Error;
use super::Repository;
use crate::database::Queryer;
use crate::domain::typ::entity;
use crate::domain::typ::model::{self};
use crate::scalar::{Id, Time};
impl Repository {
pub async fn typ_viele_erstellen<'c, C: Queryer<'c>>(
&self,
db: C,
typen: &[entity::Typ], // input: &[Typ],
) -> Result<Vec<model::Typ>, Error> {
let id: Vec<Id> = typen.iter().map(|t| t.id).collect();
let typnamen: Vec<String> = typen.iter().map(|t| t.typname.clone()).collect();
let erstellt_am: Vec<Option<Time>> = typen.iter().map(|t| t.erstellt_am).collect();
let geaendert_am: Vec<Option<Time>> = typen.iter().map(|t| t.geaendert_am).collect();
const QUERY: &str = r#"
INSERT INTO typen (id, typname, erstellt_am, geaendert_am)
SELECT * FROM UNNEST(
$1::uuid[],
$2::text[],
$3::TIMESTAMP[],
$4::TIMESTAMP[]
) RETURNING id, erstellt_am, geaendert_am, typname;
"#;
let rows = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(id)
.bind(typnamen)
.bind(erstellt_am)
.bind(geaendert_am)
.fetch_all(db)
.await?;
Ok(rows)
}
}

View File

@@ -0,0 +1,29 @@
use anyhow::Error;
use super::Repository;
use crate::database::Queryer;
use crate::domain::typ::{entity, model};
impl Repository {
pub async fn typ_erstellen<'c, C: Queryer<'c>>(
&self,
db: C,
typ: &entity::Typ,
) -> Result<model::Typ, Error> {
const QUERY: &str = r#"
INSERT INTO typen (id, erstellt_am, geaendert_am, typname) VALUES (
$1, $2, $3, $4
) RETURNING id, erstellt_am, geaendert_am, typname;
"#;
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(typ.id)
.bind(typ.erstellt_am)
.bind(typ.geaendert_am)
.bind(&typ.typname)
.fetch_one(db)
.await?;
Ok(typ)
}
}

View File

@@ -0,0 +1,24 @@
use anyhow::Error;
use super::Repository;
use crate::database::Queryer;
use crate::domain::typ::{entity, model};
impl Repository {
pub async fn typ_loeschen<'c, C: Queryer<'c>>(
&self,
db: C,
typ: &entity::Typ,
) -> Result<model::Typ, Error> {
const QUERY: &str = r#"
DELETE FROM typen WHERE id=$1 RETURNING id, erstellt_am, geaendert_am, typname;
"#;
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(typ.id)
.fetch_one(db)
.await?;
Ok(typ)
}
}

View File

@@ -0,0 +1,28 @@
use anyhow::Error;
use super::Repository;
use crate::database::Queryer;
use crate::domain::typ::{entity, model};
impl Repository {
pub async fn typ_update<'c, C: Queryer<'c>>(
&self,
db: C,
typ: &entity::Typ,
) -> Result<model::Typ, Error> {
const QUERY: &str = r#"
UPDATE typen
SET geaendert_am = $2, typname = $3 WHERE id = $1
RETURNING id, erstellt_am, geaendert_am, typname;
"#;
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
.bind(typ.id)
.bind(typ.geaendert_am)
.bind(&typ.typname)
.fetch_one(db)
.await?;
Ok(typ)
}
}

21
src/domain/typ/service.rs Normal file
View File

@@ -0,0 +1,21 @@
mod typ_alle;
// mod typ_dataloader;
mod typ_erstellen;
mod typ_loeschen;
mod typ_update;
use super::repository::Repository;
use crate::database::DB;
#[derive(Debug)]
pub struct Service {
repo: Repository,
pub db: DB,
}
impl Service {
pub fn new(db: DB) -> Self {
let repo = Repository::new();
Self { repo, db }
}
}

View File

@@ -0,0 +1,12 @@
use anyhow::Error;
use crate::domain::typ::model;
use super::Service;
impl Service {
pub async fn typ_alle(&self) -> Result<Vec<model::Typ>, Error> {
let typ = self.repo.typ_alle(&self.db).await?;
Ok(typ)
}
}

View File

@@ -0,0 +1,15 @@
use std::{collections::HashMap, sync::Arc};
use crate::{domain::typ::model::Typ, scalar::Id};
use super::Service;
impl Service {
pub async fn typ_dataloader(
&self,
keys: &[Id],
) -> Result<HashMap<Id, Vec<Typ>>, Arc<sqlx::Error>> {
let typen_dataloader = self.repo.typ_dataloader(&self.db, keys).await?;
Ok(typen_dataloader)
}
}

View File

@@ -0,0 +1,23 @@
use anyhow::Error;
use chrono::Utc;
use ulid::Ulid;
use super::Service;
use crate::domain::typ::{
entity,
model::{self, TypErstelleInput},
};
impl Service {
pub async fn typ_erstellen(&self, input: TypErstelleInput) -> Result<model::Typ, Error> {
let typ_input = entity::Typ {
id: Ulid::new().into(),
typname: input.typname,
erstellt_am: Some(Utc::now()),
geaendert_am: Some(Utc::now()),
};
let typ = self.repo.typ_erstellen(&self.db, &typ_input).await?;
Ok(typ)
}
}

View File

@@ -0,0 +1,33 @@
use anyhow::Error;
use chrono::Utc;
use ulid::Ulid;
use super::Service;
use crate::domain::typ::{
entity,
model::{self, TypErstelleInput},
};
impl Service {
pub async fn typ_erstellen_viele(
&self,
input: &[TypErstelleInput],
) -> Result<Vec<model::Typ>, Error> {
let typen_input: Vec<entity::Typ> = input
.iter()
.map(|t| entity::Typ {
id: Ulid::new().into(),
typname: t.typname.clone(),
erstellt_am: Some(Utc::now()),
geaendert_am: Some(Utc::now()),
})
.collect();
let typen = self
.repo
.typ_viele_erstellen(&self.db, &typen_input)
.await?;
Ok(typen)
}
}

View File

@@ -0,0 +1,22 @@
use anyhow::Error;
use chrono::Utc;
use super::Service;
use crate::domain::typ::{
entity,
model::{self, TypLoeschenInput},
};
impl Service {
pub async fn typ_loeschen(&self, input: TypLoeschenInput) -> Result<model::Typ, Error> {
let typ_input = entity::Typ {
id: input.id,
typname: String::new(),
erstellt_am: None,
geaendert_am: Some(Utc::now()),
};
let typ = self.repo.typ_loeschen(&self.db, &typ_input).await?;
Ok(typ)
}
}

View File

@@ -0,0 +1,22 @@
use anyhow::Error;
use chrono::Utc;
use super::Service;
use crate::domain::typ::{
entity,
model::{self, TypUpdateInput},
};
impl Service {
pub async fn typ_update(&self, input: TypUpdateInput) -> Result<model::Typ, Error> {
let typ_input = entity::Typ {
id: input.id,
typname: input.typname,
erstellt_am: None,
geaendert_am: Some(Utc::now()),
};
let typ = self.repo.typ_update(&self.db, &typ_input).await?;
Ok(typ)
}
}

View File

@@ -1,7 +1,8 @@
use crate::{
models::typ::{Typ, TypCreateInput, TypUpdateInput},
scalar::Id,
use crate::domain::typ::{
model::{Typ, TypErstelleInput, TypLoeschenInput, TypUpdateInput},
service::Service,
};
use async_graphql::{Context, FieldResult};
use sqlx::postgres::PgPool;
@@ -10,31 +11,36 @@ pub struct TypMutation;
#[async_graphql::Object]
impl TypMutation {
async fn create_typ(&self, ctx: &Context<'_>, input: TypCreateInput) -> FieldResult<Typ> {
async fn erstelle_typ(&self, ctx: &Context<'_>, input: TypErstelleInput) -> FieldResult<Typ> {
let pool = ctx.data::<PgPool>()?;
let row = Typ::create(pool, &input).await?;
Ok(row)
let typ = Service::new(pool.clone()).typ_erstellen(input).await?;
Ok(typ)
}
async fn create_many_typen(
async fn erstelle_viele_typen(
&self,
ctx: &Context<'_>,
input: Vec<TypCreateInput>,
input: Vec<TypErstelleInput>,
) -> FieldResult<Vec<Typ>> {
let pool = ctx.data::<PgPool>()?;
let row = Typ::create_many(pool, &input).await?;
Ok(row)
let gruppe = Service::new(pool.clone())
.typ_erstellen_viele(&input)
.await?;
Ok(gruppe)
}
async fn update_typ(&self, ctx: &Context<'_>, input: TypUpdateInput) -> FieldResult<Typ> {
let pool = ctx.data::<PgPool>()?;
let row = Typ::update(pool, &input).await?;
Ok(row)
let typ = Service::new(pool.clone()).typ_update(input).await?;
Ok(typ)
}
async fn delete_typ(&self, ctx: &Context<'_>, id: Id) -> FieldResult<bool> {
async fn loesche_typ(&self, ctx: &Context<'_>, input: TypLoeschenInput) -> FieldResult<Typ> {
let pool = ctx.data::<PgPool>()?;
Ok(Typ::delete(pool, &id).await?)
let typ = Service::new(pool.clone()).typ_loeschen(input).await?;
Ok(typ)
}
}

View File

@@ -1,7 +1,7 @@
use async_graphql::{Context, FieldResult, Object};
use sqlx::postgres::PgPool;
use crate::{models::typ::Typ, scalar::Id};
use crate::domain::typ::{model::Typ, service::Service};
#[derive(Default)]
pub struct TypQuery {}
@@ -10,13 +10,7 @@ pub struct TypQuery {}
impl TypQuery {
async fn typen(&self, ctx: &Context<'_>) -> FieldResult<Vec<Typ>> {
let pool = ctx.data::<PgPool>()?;
let rows = Typ::read_all(pool).await?;
Ok(rows)
}
async fn typ(&self, ctx: &Context<'_>, id: Id) -> FieldResult<Typ> {
let pool = ctx.data::<PgPool>()?;
let row = Typ::read_one(pool, &id).await?;
Ok(row)
let typen = Service::new(pool.clone()).typ_alle().await?;
Ok(typen)
}
}