ulid als scalar hinzugefügt
This commit is contained in:
1
migrations/20240530183519_init.down.sql
Normal file
1
migrations/20240530183519_init.down.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DROP DOMAIN ULID CASCADE;
|
||||||
1
migrations/20240530183519_init.up.sql
Normal file
1
migrations/20240530183519_init.up.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CREATE DOMAIN ULID AS CHAR(26);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
CREATE TABLE IF NOT EXISTS typen (
|
CREATE TABLE IF NOT EXISTS typen (
|
||||||
typ_id UUID PRIMARY KEY,
|
typ_id UUID PRIMARY KEY,
|
||||||
id CHAR(26) UNIQUE NOT NULL,
|
-- id CHAR(26) UNIQUE NOT NULL,
|
||||||
|
id ULID UNIQUE NOT NULL,
|
||||||
typname VARCHAR NOT NULL,
|
typname VARCHAR NOT NULL,
|
||||||
erstellt_am TIMESTAMP WITH TIME ZONE NOT NULL,
|
erstellt_am TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
geaendert_am TIMESTAMP WITH TIME ZONE NOT NULL
|
geaendert_am TIMESTAMP WITH TIME ZONE NOT NULL
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ use async_graphql::{ComplexObject, SimpleObject};
|
|||||||
|
|
||||||
use crate::scalar::{Time, Ulid};
|
use crate::scalar::{Time, Ulid};
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, SimpleObject, Clone, Debug)]
|
// #[derive(Debug, sqlx::FromRow)]
|
||||||
#[graphql(complex)]
|
// pub struct TypDb {
|
||||||
|
// pub id: String,
|
||||||
|
// pub typname: String,
|
||||||
|
// pub erstellt_am: Time,
|
||||||
|
// pub geaendert_am: Time,
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(SimpleObject, Clone, sqlx::FromRow)]
|
||||||
pub struct Typ {
|
pub struct Typ {
|
||||||
/// Die Ulid eines Gerätetypen
|
/// Die Ulid eines Gerätetypen
|
||||||
pub id: Ulid,
|
pub id: Ulid,
|
||||||
@@ -18,5 +25,16 @@ pub struct Typ {
|
|||||||
pub geaendert_am: Time,
|
pub geaendert_am: Time,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl From<TypDb> for Typ {
|
||||||
|
// fn from(db: TypDb) -> Self {
|
||||||
|
// Self {
|
||||||
|
// id: Ulid(ulid::Ulid::from_string(&db.id).expect("stored ULID is valid")),
|
||||||
|
// typname: db.typname,
|
||||||
|
// erstellt_am: db.erstellt_am,
|
||||||
|
// geaendert_am: db.geaendert_am,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
#[ComplexObject]
|
#[ComplexObject]
|
||||||
impl Typ {}
|
impl Typ {}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ impl Repository {
|
|||||||
|
|
||||||
let typen = sqlx::query_as::<_, model::Typ>(QUERY).fetch_all(db).await?;
|
let typen = sqlx::query_as::<_, model::Typ>(QUERY).fetch_all(db).await?;
|
||||||
|
|
||||||
|
// let typen: Vec<model::Typ> = typen.into_iter().map(model::Typ::from).collect();
|
||||||
|
|
||||||
Ok(typen)
|
Ok(typen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ impl Repository {
|
|||||||
.bind(id)
|
.bind(id)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
// .map(model::Typ::from)?;
|
||||||
|
|
||||||
Ok(row)
|
Ok(row)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ impl Repository {
|
|||||||
typen: &[entity::TypErstellen], // input: &[Typ],
|
typen: &[entity::TypErstellen], // input: &[Typ],
|
||||||
) -> Result<Vec<model::Typ>, Error> {
|
) -> Result<Vec<model::Typ>, Error> {
|
||||||
let typ_id: Vec<Id> = typen.iter().map(|t| t.typ_id).collect();
|
let typ_id: Vec<Id> = typen.iter().map(|t| t.typ_id).collect();
|
||||||
let id: Vec<Ulid> = typen.iter().map(|t| t.id.to_string()).collect();
|
let id: Vec<Ulid> = typen.iter().map(|t| t.id).collect();
|
||||||
let typnamen: Vec<String> = typen.iter().map(|t| t.typname.clone()).collect();
|
let typnamen: Vec<String> = typen.iter().map(|t| t.typname.clone()).collect();
|
||||||
let erstellt_am: Vec<Time> = typen.iter().map(|t| t.erstellt_am).collect();
|
let erstellt_am: Vec<Time> = typen.iter().map(|t| t.erstellt_am).collect();
|
||||||
let geaendert_am: Vec<Time> = typen.iter().map(|t| t.geaendert_am).collect();
|
let geaendert_am: Vec<Time> = typen.iter().map(|t| t.geaendert_am).collect();
|
||||||
@@ -38,6 +38,8 @@ impl Repository {
|
|||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// let rows: Vec<model::Typ> = rows.into_iter().map(model::Typ::from).collect();
|
||||||
|
|
||||||
Ok(rows)
|
Ok(rows)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ impl Repository {
|
|||||||
|
|
||||||
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
|
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
|
||||||
.bind(typ.typ_id)
|
.bind(typ.typ_id)
|
||||||
.bind(typ.id.to_string())
|
.bind(typ.id)
|
||||||
.bind(typ.erstellt_am)
|
.bind(typ.erstellt_am)
|
||||||
.bind(typ.geaendert_am)
|
.bind(typ.geaendert_am)
|
||||||
.bind(&typ.typname)
|
.bind(&typ.typname)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
// .map(model::Typ::from)?;
|
||||||
|
|
||||||
Ok(typ)
|
Ok(typ)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ impl Repository {
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
|
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
|
||||||
.bind(typ.id.to_string())
|
.bind(typ.id)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
// .map(model::Typ::from)?;
|
||||||
|
|
||||||
Ok(typ)
|
Ok(typ)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ impl Repository {
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
|
let typ = sqlx::query_as::<_, model::Typ>(QUERY)
|
||||||
.bind(typ.id.to_string())
|
.bind(typ.id)
|
||||||
.bind(typ.geaendert_am)
|
.bind(typ.geaendert_am)
|
||||||
.bind(&typ.typname)
|
.bind(&typ.typname)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
// .map(model::Typ::from)?;
|
||||||
|
|
||||||
Ok(typ)
|
Ok(typ)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use ulid::Ulid;
|
|
||||||
|
|
||||||
use super::Service;
|
use super::Service;
|
||||||
use crate::domain::typ::{
|
use crate::domain::typ::{
|
||||||
entity,
|
entity,
|
||||||
model::{self, TypErstelleInput},
|
model::{self, TypErstelleInput},
|
||||||
};
|
};
|
||||||
|
use crate::scalar::Ulid;
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
pub async fn typ_erstellen(&self, input: TypErstelleInput) -> Result<model::Typ, Error> {
|
pub async fn typ_erstellen(&self, input: TypErstelleInput) -> Result<model::Typ, Error> {
|
||||||
let typ_erstellen = entity::TypErstellen {
|
let typ_erstellen = entity::TypErstellen {
|
||||||
typ_id: Ulid::new().into(),
|
typ_id: ulid::Ulid::new().into(),
|
||||||
id: Ulid::new().to_string(),
|
id: Ulid(ulid::Ulid::new()),
|
||||||
typname: input.typname,
|
typname: input.typname,
|
||||||
erstellt_am: Utc::now(),
|
erstellt_am: Utc::now(),
|
||||||
geaendert_am: Utc::now(),
|
geaendert_am: Utc::now(),
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use ulid::Ulid;
|
|
||||||
|
|
||||||
use super::Service;
|
use super::Service;
|
||||||
use crate::domain::typ::{
|
use crate::{
|
||||||
entity,
|
domain::typ::{
|
||||||
model::{self, TypErstelleInput},
|
entity,
|
||||||
|
model::{self, TypErstelleInput},
|
||||||
|
},
|
||||||
|
scalar::Ulid,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
@@ -16,8 +18,8 @@ impl Service {
|
|||||||
let typen_erstellen: Vec<entity::TypErstellen> = input
|
let typen_erstellen: Vec<entity::TypErstellen> = input
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| entity::TypErstellen {
|
.map(|t| entity::TypErstellen {
|
||||||
typ_id: Ulid::new().into(),
|
typ_id: ulid::Ulid::new().into(),
|
||||||
id: Ulid::new().to_string(),
|
id: Ulid(ulid::Ulid::new()),
|
||||||
typname: t.typname.clone(),
|
typname: t.typname.clone(),
|
||||||
erstellt_am: Utc::now(),
|
erstellt_am: Utc::now(),
|
||||||
geaendert_am: Utc::now(),
|
geaendert_am: Utc::now(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use async_graphql::*;
|
||||||
/// It is easier to track each type alias if this file is located on the top-level directory (here)
|
/// It is easier to track each type alias if this file is located on the top-level directory (here)
|
||||||
/// than in each domain. Also, separating them will create a lot of duplicate code.
|
/// than in each domain. Also, separating them will create a lot of duplicate code.
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@@ -9,7 +10,95 @@ pub type Time = chrono::DateTime<chrono::Utc>;
|
|||||||
/// When expected as an input type, any string (such as "4") or integer (such as 4) input value will be accepted as an ID.
|
/// When expected as an input type, any string (such as "4") or integer (such as 4) input value will be accepted as an ID.
|
||||||
pub type Id = Uuid;
|
pub type Id = Uuid;
|
||||||
|
|
||||||
/// ödsklfjsdf
|
// pub type Ulid = String;
|
||||||
/// debug_assert_eq!(ödsklfjsdfasdfas
|
|
||||||
/// ödsklfjsdfasdfasdfsa,
|
// pub type Ulid = ulid::Ulid;
|
||||||
pub type Ulid = String;
|
|
||||||
|
// #[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
// pub struct Ulid(pub ulid::Ulid);
|
||||||
|
//
|
||||||
|
// /// The ULID scalar type represents a Universally Unique Lexicographically Sortable Identifier as defined by the ULID specification.
|
||||||
|
// /// ULIDs are 26-character strings that are URL-safe, case-insensitive, and lexicographically sortable,
|
||||||
|
// /// making them ideal for distributed systems requiring time-ordered unique identifiers.
|
||||||
|
// #[Scalar]
|
||||||
|
// impl ScalarType for Ulid {
|
||||||
|
// fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
|
// match value {
|
||||||
|
// Value::String(s) => {
|
||||||
|
// let ulid = ulid::Ulid::from_string(&s).map_err(InputValueError::custom)?;
|
||||||
|
// Ok(Ulid(ulid))
|
||||||
|
// }
|
||||||
|
// _ => Err(InputValueError::expected_type(value)),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn to_value(&self) -> Value {
|
||||||
|
// Value::String(self.0.to_string())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl fmt::Display for Ulid {
|
||||||
|
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// write!(f, "{}", self.0) // delegiert an Ulid::to_string()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
use sqlx::{
|
||||||
|
postgres::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef},
|
||||||
|
Decode, Encode, Postgres, Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
// #[derive(sqlx::Type)]
|
||||||
|
// #[sqlx(type_name = "ULID")]
|
||||||
|
// #[sqlx(no_pg_array)]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Ulid(pub ulid::Ulid);
|
||||||
|
|
||||||
|
#[Scalar]
|
||||||
|
impl ScalarType for Ulid {
|
||||||
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
|
match value {
|
||||||
|
Value::String(s) => {
|
||||||
|
let ulid = ulid::Ulid::from_string(&s).map_err(InputValueError::custom)?;
|
||||||
|
Ok(Ulid(ulid))
|
||||||
|
}
|
||||||
|
_ => Err(InputValueError::expected_type(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
Value::String(self.0.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type<Postgres> for Ulid {
|
||||||
|
fn type_info() -> PgTypeInfo {
|
||||||
|
PgTypeInfo::with_name("ULID")
|
||||||
|
}
|
||||||
|
fn compatible(ty: &PgTypeInfo) -> bool {
|
||||||
|
<String as Type<Postgres>>::compatible(ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'r> Decode<'r, Postgres> for Ulid {
|
||||||
|
fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let s = <String as Decode<Postgres>>::decode(value)?;
|
||||||
|
|
||||||
|
Ok(Ulid(ulid::Ulid::from_string(s.trim())?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'q> Encode<'q, Postgres> for Ulid {
|
||||||
|
fn encode_by_ref(
|
||||||
|
&self,
|
||||||
|
buf: &mut PgArgumentBuffer,
|
||||||
|
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
<String as Encode<Postgres>>::encode(self.0.to_string(), buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PgHasArrayType for Ulid {
|
||||||
|
fn array_type_info() -> PgTypeInfo {
|
||||||
|
PgTypeInfo::with_name("_ULID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
23
src/sqlx_datatype.rs
Normal file
23
src/sqlx_datatype.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// // SQLx sieht den Typ als Text (PostgreSQL: CHAR, VARCHAR, TEXT) impl Type<Postgres> for Ulid {
|
||||||
|
// fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||||
|
// // CHAR(26) ist in PostgreSQL intern ein Text-Typ
|
||||||
|
// <String as Type<Postgres>>::type_info()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl<'r> Decode<'r, Postgres> for MyUlid {
|
||||||
|
// fn decode(
|
||||||
|
// value: sqlx::postgres::PgValueRef<'r>,
|
||||||
|
// ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
|
||||||
|
// let s = <String as Decode<Postgres>>::decode(value)?;
|
||||||
|
// Ulid::from_string(&s)
|
||||||
|
// .map(MyUlid)
|
||||||
|
// .map_err(|e| format!("Invalid ULID: {}", e).into())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl<'q> Encode<'q, Postgres> for MyUlid {
|
||||||
|
// fn encode_by_ref(&self, buf: &mut sqlx::postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
|
||||||
|
// <String as Encode<Postgres>>::encode(self.0.to_string(), buf)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
Reference in New Issue
Block a user