about summary refs log tree commit diff stats
path: root/crates/rocie-server/src/storage/sql
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rocie-server/src/storage/sql')
-rw-r--r--crates/rocie-server/src/storage/sql/barcode.rs9
-rw-r--r--crates/rocie-server/src/storage/sql/get/barcode/mod.rs4
-rw-r--r--crates/rocie-server/src/storage/sql/get/mod.rs1
-rw-r--r--crates/rocie-server/src/storage/sql/get/product/mod.rs11
-rw-r--r--crates/rocie-server/src/storage/sql/get/product_amount/mod.rs5
-rw-r--r--crates/rocie-server/src/storage/sql/get/unit/mod.rs11
-rw-r--r--crates/rocie-server/src/storage/sql/get/unit_property/mod.rs117
-rw-r--r--crates/rocie-server/src/storage/sql/insert/barcode/mod.rs9
-rw-r--r--crates/rocie-server/src/storage/sql/insert/mod.rs1
-rw-r--r--crates/rocie-server/src/storage/sql/insert/product/mod.rs17
-rw-r--r--crates/rocie-server/src/storage/sql/insert/unit/mod.rs22
-rw-r--r--crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs106
-rw-r--r--crates/rocie-server/src/storage/sql/mod.rs95
-rw-r--r--crates/rocie-server/src/storage/sql/product.rs88
-rw-r--r--crates/rocie-server/src/storage/sql/product_amount.rs2
-rw-r--r--crates/rocie-server/src/storage/sql/unit.rs100
-rw-r--r--crates/rocie-server/src/storage/sql/unit_property.rs23
17 files changed, 449 insertions, 172 deletions
diff --git a/crates/rocie-server/src/storage/sql/barcode.rs b/crates/rocie-server/src/storage/sql/barcode.rs
index 1c3c55a..f15d399 100644
--- a/crates/rocie-server/src/storage/sql/barcode.rs
+++ b/crates/rocie-server/src/storage/sql/barcode.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Serialize};
 use utoipa::ToSchema;
 
-use crate::storage::sql::unit::UnitId;
+use crate::storage::sql::unit::UnitAmount;
 
 #[derive(ToSchema, Debug, Clone, Serialize, Deserialize)]
 pub(crate) struct Barcode {
@@ -41,10 +41,3 @@ impl From<BarcodeIdStub> for BarcodeId {
         Self { value: value.value }
     }
 }
-
-#[derive(ToSchema, Debug, Clone, Copy, Serialize, Deserialize)]
-pub(crate) struct UnitAmount {
-    #[schema(minimum = 0)]
-    pub(crate) value: u32,
-    pub(crate) unit: UnitId,
-}
diff --git a/crates/rocie-server/src/storage/sql/get/barcode/mod.rs b/crates/rocie-server/src/storage/sql/get/barcode/mod.rs
index 7b656b1..4eba105 100644
--- a/crates/rocie-server/src/storage/sql/get/barcode/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/barcode/mod.rs
@@ -1,8 +1,8 @@
 use crate::{
     app::App,
     storage::sql::{
-        barcode::{Barcode, BarcodeId, UnitAmount},
-        unit::UnitId,
+        barcode::{Barcode, BarcodeId},
+        unit::{UnitAmount, UnitId},
     },
 };
 
diff --git a/crates/rocie-server/src/storage/sql/get/mod.rs b/crates/rocie-server/src/storage/sql/get/mod.rs
index 048cb3d..62047b8 100644
--- a/crates/rocie-server/src/storage/sql/get/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/mod.rs
@@ -1,4 +1,5 @@
 pub(crate) mod product;
 pub(crate) mod product_amount;
 pub(crate) mod unit;
+pub(crate) mod unit_property;
 pub(crate) mod barcode;
diff --git a/crates/rocie-server/src/storage/sql/get/product/mod.rs b/crates/rocie-server/src/storage/sql/get/product/mod.rs
index 541f388..01047b2 100644
--- a/crates/rocie-server/src/storage/sql/get/product/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/product/mod.rs
@@ -1,9 +1,10 @@
 use crate::{
     app::App,
     storage::sql::{
-        barcode::{Barcode, BarcodeId, UnitAmount},
+        barcode::{Barcode, BarcodeId},
         product::{Product, ProductId},
-        unit::UnitId,
+        unit::{UnitAmount, UnitId},
+        unit_property::UnitPropertyId,
     },
 };
 
@@ -13,7 +14,7 @@ impl Product {
     pub(crate) async fn from_id(app: &App, id: ProductId) -> Result<Option<Self>, from_id::Error> {
         let record = query!(
             "
-        SELECT name, description, parent
+        SELECT name, unit_property, description, parent
         FROM products
         WHERE id = ?
 ",
@@ -25,6 +26,7 @@ impl Product {
         if let Some(record) = record {
             Ok(Some(Self {
                 id,
+                unit_property: UnitPropertyId::from_db(&record.unit_property),
                 name: record.name,
                 description: record.description,
                 associated_bar_codes: vec![], // todo
@@ -37,7 +39,7 @@ impl Product {
     pub(crate) async fn get_all(app: &App) -> Result<Vec<Self>, get_all::Error> {
         let records = query!(
             "
-        SELECT id, name, description, parent
+        SELECT id, unit_property, name, description, parent
         FROM products
 "
         )
@@ -59,6 +61,7 @@ impl Product {
 
             all.push(Self {
                 id: ProductId::from_db(&record.id),
+                unit_property: UnitPropertyId::from_db(&record.unit_property),
                 name: record.name,
                 description: record.description,
                 associated_bar_codes: barcodes
diff --git a/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs b/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs
index 7700274..f82c2a0 100644
--- a/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/product_amount/mod.rs
@@ -1,8 +1,6 @@
 use crate::{
     app::App,
-    storage::sql::{
-        barcode::UnitAmount, product::ProductId, product_amount::ProductAmount, unit::UnitId,
-    },
+    storage::sql::{product::ProductId, product_amount::ProductAmount, unit::{UnitAmount, UnitId}},
 };
 
 use sqlx::query;
@@ -20,6 +18,7 @@ SELECT
 FROM barcodes
     JOIN products ON products.id = ?
     JOIN buys ON buys.barcode_id = barcodes.id
+WHERE barcodes.product_id = products.id
 GROUP BY barcodes.unit;
 "#,
             product_id
diff --git a/crates/rocie-server/src/storage/sql/get/unit/mod.rs b/crates/rocie-server/src/storage/sql/get/unit/mod.rs
index 6c2bbcc..6f5d297 100644
--- a/crates/rocie-server/src/storage/sql/get/unit/mod.rs
+++ b/crates/rocie-server/src/storage/sql/get/unit/mod.rs
@@ -1,6 +1,9 @@
 use crate::{
     app::App,
-    storage::sql::unit::{Unit, UnitId},
+    storage::sql::{
+        unit::{Unit, UnitId},
+        unit_property::UnitPropertyId,
+    },
 };
 
 use sqlx::query;
@@ -9,7 +12,7 @@ impl Unit {
     pub(crate) async fn get_all(app: &App) -> Result<Vec<Self>, get_all::Error> {
         let records = query!(
             "
-        SELECT id, full_name_singular, full_name_plural, short_name, description
+        SELECT id, unit_property, full_name_singular, full_name_plural, short_name, description
         FROM units
 "
         )
@@ -20,6 +23,7 @@ impl Unit {
             .into_iter()
             .map(|record| Self {
                 id: UnitId::from_db(&record.id),
+                unit_property: UnitPropertyId::from_db(&record.unit_property),
                 full_name_singular: record.full_name_singular,
                 full_name_plural: record.full_name_plural,
                 short_name: record.short_name,
@@ -31,7 +35,7 @@ impl Unit {
     pub(crate) async fn from_id(app: &App, id: UnitId) -> Result<Option<Self>, from_id::Error> {
         let record = query!(
             "
-        SELECT full_name_singular, full_name_plural, short_name, description
+        SELECT full_name_singular, unit_property, full_name_plural, short_name, description
         FROM units
         WHERE id = ?
 ",
@@ -43,6 +47,7 @@ impl Unit {
         if let Some(record) = record {
             Ok(Some(Self {
                 id,
+                unit_property: UnitPropertyId::from_db(&record.unit_property),
                 full_name_singular: record.full_name_singular,
                 full_name_plural: record.full_name_plural,
                 short_name: record.short_name,
diff --git a/crates/rocie-server/src/storage/sql/get/unit_property/mod.rs b/crates/rocie-server/src/storage/sql/get/unit_property/mod.rs
new file mode 100644
index 0000000..be24181
--- /dev/null
+++ b/crates/rocie-server/src/storage/sql/get/unit_property/mod.rs
@@ -0,0 +1,117 @@
+use crate::{
+    app::App,
+    storage::sql::{
+        unit::UnitId,
+        unit_property::{UnitProperty, UnitPropertyId},
+    },
+};
+
+use sqlx::query;
+
+impl UnitProperty {
+    pub(crate) async fn get_all(app: &App) -> Result<Vec<Self>, get_all::Error> {
+        let records = query!(
+            "
+        SELECT id, name, description
+        FROM unit_properties
+"
+        )
+        .fetch_all(&app.db)
+        .await?;
+
+        let mut output = Vec::with_capacity(records.len());
+        for record in records {
+            let units = query!(
+                "
+            SELECT id
+            FROM units
+            WHERE units.unit_property = ?
+                ",
+                record.id
+            )
+            .fetch_all(&app.db)
+            .await?;
+
+            let units = units
+                .into_iter()
+                .map(|record| UnitId::from_db(&record.id))
+                .collect();
+
+            output.push(Self {
+                id: UnitPropertyId::from_db(&record.id),
+                units,
+                name: record.name,
+                description: record.description,
+            })
+        }
+
+        Ok(output)
+    }
+
+    pub(crate) async fn from_id(
+        app: &App,
+        id: UnitPropertyId,
+    ) -> Result<Option<Self>, from_id::Error> {
+        let record = query!(
+            "
+        SELECT name, description
+        FROM unit_properties
+        WHERE id = ?
+",
+            id
+        )
+        .fetch_optional(&app.db)
+        .await?;
+
+        if let Some(record) = record {
+            let units = query!(
+                "
+            SELECT id
+            FROM units
+            WHERE units.unit_property = ?
+                ",
+                id
+            )
+            .fetch_all(&app.db)
+            .await?;
+
+            let units = units
+                .into_iter()
+                .map(|record| UnitId::from_db(&record.id))
+                .collect();
+
+            Ok(Some(Self {
+                id,
+                units,
+                name: record.name,
+                description: record.description,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+}
+
+pub(crate) mod get_all {
+    use actix_web::ResponseError;
+
+    #[derive(thiserror::Error, Debug)]
+    pub(crate) enum Error {
+        #[error("Failed to execute the sql query")]
+        SqlError(#[from] sqlx::Error),
+    }
+
+    impl ResponseError for Error {}
+}
+
+pub(crate) mod from_id {
+    use actix_web::ResponseError;
+
+    #[derive(thiserror::Error, Debug)]
+    pub(crate) enum Error {
+        #[error("Failed to execute the sql query")]
+        SqlError(#[from] sqlx::Error),
+    }
+
+    impl ResponseError for Error {}
+}
diff --git a/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs b/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs
index 62a2e11..11707b9 100644
--- a/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/barcode/mod.rs
@@ -9,9 +9,9 @@ use crate::{
     storage::{
         migrate::get_current_date,
         sql::{
-            barcode::{Barcode, BarcodeId, UnitAmount},
+            barcode::{Barcode, BarcodeId},
             insert::{Operations, Transactionable},
-            unit::Unit,
+            unit::{Unit, UnitAmount},
         },
     },
 };
@@ -208,10 +208,7 @@ pub(crate) mod consume {
 
     use crate::storage::{
         self,
-        sql::{
-            barcode::UnitAmount,
-            unit::{Unit, UnitId},
-        },
+        sql::unit::{Unit, UnitAmount, UnitId},
     };
 
     #[derive(thiserror::Error, Debug)]
diff --git a/crates/rocie-server/src/storage/sql/insert/mod.rs b/crates/rocie-server/src/storage/sql/insert/mod.rs
index e6728d9..3b2d702 100644
--- a/crates/rocie-server/src/storage/sql/insert/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/mod.rs
@@ -10,6 +10,7 @@ use sqlx::{SqliteConnection, query};
 pub(crate) mod barcode;
 pub(crate) mod product;
 pub(crate) mod unit;
+pub(crate) mod unit_property;
 
 pub(crate) trait Transactionable:
     Sized + std::fmt::Debug + Serialize + DeserializeOwned
diff --git a/crates/rocie-server/src/storage/sql/insert/product/mod.rs b/crates/rocie-server/src/storage/sql/insert/product/mod.rs
index e14d3f4..d762e9b 100644
--- a/crates/rocie-server/src/storage/sql/insert/product/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/product/mod.rs
@@ -6,6 +6,7 @@ use crate::storage::sql::{
     barcode::Barcode,
     insert::{Operations, Transactionable},
     product::{Product, ProductId},
+    unit_property::UnitPropertyId,
 };
 
 #[derive(Debug, Deserialize, Serialize)]
@@ -15,6 +16,7 @@ pub(crate) enum Operation {
         name: String,
         description: Option<String>,
         parent: Option<ProductId>,
+        unit_property: UnitPropertyId,
     },
     AssociateBarcode {
         id: ProductId,
@@ -33,13 +35,15 @@ impl Transactionable for Operation {
                 name,
                 description,
                 parent,
+                unit_property,
             } => {
                 query!(
                     "
-                    INSERT INTO products (id, name, description, parent)
-                    VALUES (?,?,?,?)
+                    INSERT INTO products (id, unit_property, name, description, parent)
+                    VALUES (?,?,?,?,?)
 ",
                     id,
+                    unit_property,
                     name,
                     description,
                     parent
@@ -75,17 +79,19 @@ impl Transactionable for Operation {
                 id,
                 name,
                 description,
+                unit_property,
                 parent,
             } => {
                 query!(
                     "
                     DELETE FROM products
-                    WHERE id = ? AND name = ? AND description = ? AND parent = ?;
+                    WHERE id = ? AND name = ? AND description = ? AND parent = ? AND unit_property = ?;
 ",
                     id,
                     name,
                     description,
-                    parent
+                    parent,
+                    unit_property
                 )
                 .execute(txn)
                 .await?;
@@ -133,6 +139,7 @@ impl Product {
         name: String,
         description: Option<String>,
         parent: Option<ProductId>,
+        unit_property: UnitPropertyId,
         ops: &mut Operations<Operation>,
     ) -> Self {
         let id = ProductId::from(Uuid::new_v4());
@@ -141,6 +148,7 @@ impl Product {
             id,
             name: name.clone(),
             description: description.clone(),
+            unit_property,
             parent,
         });
 
@@ -148,6 +156,7 @@ impl Product {
             id,
             name,
             description,
+            unit_property,
             associated_bar_codes: vec![],
         }
     }
diff --git a/crates/rocie-server/src/storage/sql/insert/unit/mod.rs b/crates/rocie-server/src/storage/sql/insert/unit/mod.rs
index ba08487..815cb1e 100644
--- a/crates/rocie-server/src/storage/sql/insert/unit/mod.rs
+++ b/crates/rocie-server/src/storage/sql/insert/unit/mod.rs
@@ -2,7 +2,11 @@ use serde::{Deserialize, Serialize};
 use sqlx::query;
 use uuid::Uuid;
 
-use crate::storage::sql::{insert::{Operations, Transactionable}, unit::{Unit, UnitId}};
+use crate::storage::sql::{
+    insert::{Operations, Transactionable},
+    unit::{Unit, UnitId},
+    unit_property::UnitPropertyId,
+};
 
 #[derive(Debug, Deserialize, Serialize)]
 pub(crate) enum Operation {
@@ -11,6 +15,7 @@ pub(crate) enum Operation {
         full_name_singular: String,
         full_name_plural: String,
         short_name: String,
+        unit_property: UnitPropertyId,
         description: Option<String>,
     },
 }
@@ -27,13 +32,14 @@ impl Transactionable for Operation {
                 full_name_plural,
                 short_name,
                 description,
+                unit_property,
             } => {
                 query!(
                     "
-                    INSERT INTO units (id, full_name_singular, full_name_plural, short_name, description)
-                    VALUES (?,?,?,?,?)
+                    INSERT INTO units (id, unit_property, full_name_singular, full_name_plural, short_name, description)
+                    VALUES (?,?,?,?,?,?)
 ",
-                    id, full_name_singular, full_name_plural, short_name, description,
+                    id, unit_property, full_name_singular, full_name_plural, short_name, description,
                 )
                 .execute(txn)
                 .await?;
@@ -50,13 +56,14 @@ impl Transactionable for Operation {
                 full_name_plural,
                 short_name,
                 description,
+                unit_property,
             } => {
                 query!(
                     "
                     DELETE FROM units
-                    WHERE id = ? AND full_name_singular = ? AND full_name_plural = ? AND short_name = ? AND description = ?;
+                    WHERE id = ? AND full_name_singular = ? AND full_name_plural = ? AND short_name = ? AND description = ? AND unit_property = ?;
 ",
-                    id, full_name_singular, full_name_plural, short_name, description,
+                    id, full_name_singular, full_name_plural, short_name, description, unit_property
                 )
                 .execute(txn)
                 .await?;
@@ -87,6 +94,7 @@ impl Unit {
         full_name_plural: String,
         short_name: String,
         description: Option<String>,
+        unit_property: UnitPropertyId,
         ops: &mut Operations<Operation>,
     ) -> Self {
         let id = UnitId::from(Uuid::new_v4());
@@ -97,6 +105,7 @@ impl Unit {
             full_name_plural: full_name_plural.clone(),
             short_name: short_name.clone(),
             description: description.clone(),
+            unit_property,
         });
 
         Self {
@@ -105,6 +114,7 @@ impl Unit {
             full_name_plural,
             short_name,
             description,
+            unit_property,
         }
     }
 }
diff --git a/crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs b/crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs
new file mode 100644
index 0000000..d340465
--- /dev/null
+++ b/crates/rocie-server/src/storage/sql/insert/unit_property/mod.rs
@@ -0,0 +1,106 @@
+use serde::{Deserialize, Serialize};
+use sqlx::query;
+use uuid::Uuid;
+
+use crate::storage::sql::{
+    insert::{Operations, Transactionable},
+    unit_property::{UnitProperty, UnitPropertyId},
+};
+
+#[derive(Debug, Deserialize, Serialize)]
+pub(crate) enum Operation {
+    RegisterUnitProperty {
+        id: UnitPropertyId,
+        name: String,
+        description: Option<String>,
+    },
+}
+
+impl Transactionable for Operation {
+    type ApplyError = apply::Error;
+    type UndoError = undo::Error;
+
+    async fn apply(self, txn: &mut sqlx::SqliteConnection) -> Result<(), apply::Error> {
+        match self {
+            Operation::RegisterUnitProperty {
+                id,
+                name,
+                description,
+            } => {
+                query!(
+                    "
+                    INSERT INTO unit_properties (id, name, description)
+                    VALUES (?,?,?)
+",
+                    id,
+                    name,
+                    description,
+                )
+                .execute(txn)
+                .await?;
+            }
+        }
+        Ok(())
+    }
+
+    async fn undo(self, txn: &mut sqlx::SqliteConnection) -> Result<(), undo::Error> {
+        match self {
+            Operation::RegisterUnitProperty {
+                id,
+                name,
+                description,
+            } => {
+                query!(
+                    "
+                    DELETE FROM unit_properties
+                    WHERE id = ? AND name = ? AND description = ?;
+",
+                    id,
+                    name,
+                    description
+                )
+                .execute(txn)
+                .await?;
+            }
+        }
+        Ok(())
+    }
+}
+
+pub(crate) mod undo {
+    #[derive(thiserror::Error, Debug)]
+    pub(crate) enum Error {
+        #[error("Failed to execute undo sql statments: {0}")]
+        SqlError(#[from] sqlx::Error),
+    }
+}
+pub(crate) mod apply {
+    #[derive(thiserror::Error, Debug)]
+    pub(crate) enum Error {
+        #[error("Failed to execute apply sql statments: {0}")]
+        SqlError(#[from] sqlx::Error),
+    }
+}
+
+impl UnitProperty {
+    pub(crate) fn register(
+        name: String,
+        description: Option<String>,
+        ops: &mut Operations<Operation>,
+    ) -> Self {
+        let id = UnitPropertyId::from(Uuid::new_v4());
+
+        ops.push(Operation::RegisterUnitProperty {
+            id,
+            name: name.clone(),
+            description: description.clone(),
+        });
+
+        Self {
+            id,
+            units: vec![],
+            name,
+            description,
+        }
+    }
+}
diff --git a/crates/rocie-server/src/storage/sql/mod.rs b/crates/rocie-server/src/storage/sql/mod.rs
index edce187..a44fbad 100644
--- a/crates/rocie-server/src/storage/sql/mod.rs
+++ b/crates/rocie-server/src/storage/sql/mod.rs
@@ -2,7 +2,100 @@ pub(crate) mod get;
 pub(crate) mod insert;
 
 // Types
+pub(crate) mod barcode;
 pub(crate) mod product;
 pub(crate) mod product_amount;
 pub(crate) mod unit;
-pub(crate) mod barcode;
+pub(crate) mod unit_property;
+
+macro_rules! mk_id {
+    ($name:ident and $stub_name:ident) => {
+        mk_id!($name and $stub_name with uuid::Uuid, "uuid::Uuid");
+    };
+    ($name:ident and $stub_name:ident with $inner:path, $inner_string:literal $($args:meta)*) => {
+        #[derive(
+            serde::Deserialize,
+            serde::Serialize,
+            Debug,
+            Default,
+            utoipa::ToSchema,
+            Clone,
+            Copy,
+            PartialEq,
+            Eq,
+            PartialOrd,
+            Ord,
+        )]
+        pub(crate) struct $name {
+            value: $inner,
+        }
+
+        #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
+        #[serde(from = $inner_string)]
+        pub(crate) struct $stub_name {
+            value: $inner,
+        }
+
+        impl $name {
+            pub(crate) fn from_db(id: &str) -> Self {
+                use std::str::FromStr;
+
+                Self {
+                    value: <$inner as FromStr>::from_str(id)
+                        .expect(
+                            concat!(
+                                "We put an ",
+                                $inner_string,
+                                " into the db, it should also go out again"
+                            )
+                        ),
+                }
+            }
+        }
+
+        impl std::fmt::Display for $name {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(f, "{}", self.value)
+            }
+        }
+
+        impl From<$inner> for $name {
+            fn from(value: $inner) -> Self {
+                Self { value }
+            }
+        }
+        impl From<$inner> for $stub_name {
+            fn from(value: $inner) -> Self {
+                Self { value }
+            }
+        }
+        impl From<$stub_name> for $name {
+            fn from(value: $stub_name) -> Self {
+                Self { value: value.value }
+            }
+        }
+
+        impl<'q, DB: sqlx::Database> sqlx::Encode<'q, DB> for $name
+        where
+            String: sqlx::Encode<'q, DB>,
+        {
+            fn encode_by_ref(
+                &self,
+                buf: &mut <DB as sqlx::Database>::ArgumentBuffer<'q>,
+            ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
+                let inner = self.value.to_string();
+                sqlx::Encode::<DB>::encode_by_ref(&inner, buf)
+            }
+        }
+        impl<DB: sqlx::Database> sqlx::Type<DB> for $name
+        where
+            String: sqlx::Type<DB>,
+        {
+            fn type_info() -> DB::TypeInfo {
+                <String as sqlx::Type<DB>>::type_info()
+            }
+        }
+    };
+}
+
+pub(crate) use mk_id;
diff --git a/crates/rocie-server/src/storage/sql/product.rs b/crates/rocie-server/src/storage/sql/product.rs
index c94fcce..2575b59 100644
--- a/crates/rocie-server/src/storage/sql/product.rs
+++ b/crates/rocie-server/src/storage/sql/product.rs
@@ -1,82 +1,32 @@
-use std::{fmt::Display, str::FromStr};
-
 use serde::{Deserialize, Serialize};
-use sqlx::{Database, Encode, Type};
 use utoipa::ToSchema;
-use uuid::Uuid;
 
-use crate::storage::sql::barcode::Barcode;
+use crate::storage::sql::{barcode::Barcode, mk_id, unit_property::UnitPropertyId};
 
+/// The base of rocie.
+///
+/// Products can be bought and consumed and represent, what you actually have in storage.
+/// Not every product is bought, as some can also be obtained by cooking a recipe.
 #[derive(Clone, ToSchema, Serialize, Deserialize)]
 pub(crate) struct Product {
+    /// The id of the product.
     pub(crate) id: ProductId,
-    pub(super) name: String,
-    pub(super) description: Option<String>,
-    pub(super) associated_bar_codes: Vec<Barcode>,
-}
-
-#[derive(
-    Deserialize, Serialize, Debug, Default, ToSchema, Clone, Copy, PartialEq, Eq, PartialOrd, Ord,
-)]
-pub(crate) struct ProductId {
-    value: Uuid,
-}
-
-#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
-#[serde(from = "Uuid")]
-pub(crate) struct ProductIdStub {
-    value: Uuid,
-}
 
-impl ProductId {
-    pub(crate) fn from_db(id: &str) -> ProductId {
-        Self {
-            value: Uuid::from_str(id)
-                .expect("We put an uuid into the db, it should also go out again"),
-        }
-    }
-}
+    /// The property this product is measured in.
+    ///
+    /// (This is probably always either Mass, Volume or Quantity).
+    pub(crate) unit_property: UnitPropertyId,
 
-impl Display for ProductId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.value)
-    }
-}
+    /// The name of the product.
+    ///
+    /// This should be globally unique, to make searching easier for the user.
+    pub(super) name: String,
 
-impl From<Uuid> for ProductId {
-    fn from(value: Uuid) -> Self {
-        Self { value }
-    }
-}
+    /// An optional description of this product.
+    pub(super) description: Option<String>,
 
-impl From<Uuid> for ProductIdStub {
-    fn from(value: Uuid) -> Self {
-        Self { value }
-    }
-}
-impl From<ProductIdStub> for ProductId {
-    fn from(value: ProductIdStub) -> Self {
-        Self { value: value.value }
-    }
+    /// Which barcodes are associated with this product.
+    pub(super) associated_bar_codes: Vec<Barcode>,
 }
 
-impl<'q, DB: Database> Encode<'q, DB> for ProductId
-where
-    String: Encode<'q, DB>,
-{
-    fn encode_by_ref(
-        &self,
-        buf: &mut <DB as Database>::ArgumentBuffer<'q>,
-    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
-        let inner = self.value.to_string();
-        Encode::<DB>::encode_by_ref(&inner, buf)
-    }
-}
-impl<DB: Database> Type<DB> for ProductId
-where
-    String: Type<DB>,
-{
-    fn type_info() -> DB::TypeInfo {
-        <String as Type<DB>>::type_info()
-    }
-}
+mk_id!(ProductId and ProductIdStub);
diff --git a/crates/rocie-server/src/storage/sql/product_amount.rs b/crates/rocie-server/src/storage/sql/product_amount.rs
index 232c5db..0f19afc 100644
--- a/crates/rocie-server/src/storage/sql/product_amount.rs
+++ b/crates/rocie-server/src/storage/sql/product_amount.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Serialize};
 use utoipa::ToSchema;
 
-use crate::storage::sql::{barcode::UnitAmount, product::ProductId};
+use crate::storage::sql::{product::ProductId, unit::UnitAmount};
 
 #[derive(Clone, ToSchema, Serialize, Deserialize)]
 pub(crate) struct ProductAmount {
diff --git a/crates/rocie-server/src/storage/sql/unit.rs b/crates/rocie-server/src/storage/sql/unit.rs
index 77e7a2e..d16e783 100644
--- a/crates/rocie-server/src/storage/sql/unit.rs
+++ b/crates/rocie-server/src/storage/sql/unit.rs
@@ -1,80 +1,50 @@
-use std::{fmt::Display, str::FromStr};
-
 use serde::{Deserialize, Serialize};
-use sqlx::{Database, Encode, Type};
 use utoipa::ToSchema;
-use uuid::Uuid;
+
+use crate::storage::sql::{mk_id, unit_property::UnitPropertyId};
 
 #[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
 pub(crate) struct Unit {
+    /// Unique id for this unit.
     pub(crate) id: UnitId,
+
+    /// The singular version of this unit's name.
+    /// E.g.:
+    ///     Kilogram
+    ///     Gram
     pub(crate) full_name_singular: String,
-    pub(crate) full_name_plural: String,
-    pub(crate) short_name: String,
-    pub(crate) description: Option<String>,
-}
 
-#[derive(
-    Deserialize, Serialize, Debug, Default, ToSchema, Clone, Copy, PartialEq, Eq, PartialOrd, Ord,
-)]
-pub(crate) struct UnitId {
-    value: Uuid,
-}
+    /// The plural version of this unit's name. Is used by a value of two and more.
+    /// (We do not support Slovenian dual numerus versions)
+    /// E.g.:
+    ///     Kilogram -> Kilograms
+    ///     gram -> meters
+    pub(crate) full_name_plural: String,
 
-#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
-#[serde(from = "Uuid")]
-pub(crate) struct UnitIdStub {
-    value: Uuid,
-}
+    /// Short name or abbreviation of this unit.
+    /// E.g.:
+    ///     kg for Kilogram
+    ///     g for gram
+    ///     m for meter
+    pub(crate) short_name: String,
 
-impl UnitId {
-    pub(crate) fn from_db(id: &str) -> UnitId {
-        Self {
-            value: Uuid::from_str(id)
-                .expect("We put an uuid into the db, it should also go out again"),
-        }
-    }
-}
+    /// Description of this unit.
+    pub(crate) description: Option<String>,
 
-impl Display for UnitId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.value)
-    }
+    /// Which property is described by this unit.
+    /// E.g.:
+    ///     kg -> Mass
+    ///     L -> Volume
+    ///     m/s -> Speed
+    ///     and so forth
+    pub(crate) unit_property: UnitPropertyId,
 }
 
-impl From<Uuid> for UnitId {
-    fn from(value: Uuid) -> Self {
-        Self { value }
-    }
-}
-impl From<Uuid> for UnitIdStub {
-    fn from(value: Uuid) -> Self {
-        Self { value }
-    }
-}
-impl From<UnitIdStub> for UnitId {
-    fn from(value: UnitIdStub) -> Self {
-        Self { value: value.value }
-    }
+#[derive(ToSchema, Debug, Clone, Copy, Serialize, Deserialize)]
+pub(crate) struct UnitAmount {
+    #[schema(minimum = 0)]
+    pub(crate) value: u32,
+    pub(crate) unit: UnitId,
 }
 
-impl<'q, DB: Database> Encode<'q, DB> for UnitId
-where
-    String: Encode<'q, DB>,
-{
-    fn encode_by_ref(
-        &self,
-        buf: &mut <DB as Database>::ArgumentBuffer<'q>,
-    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
-        let inner = self.value.to_string();
-        Encode::<DB>::encode_by_ref(&inner, buf)
-    }
-}
-impl<DB: Database> Type<DB> for UnitId
-where
-    String: Type<DB>,
-{
-    fn type_info() -> DB::TypeInfo {
-        <String as Type<DB>>::type_info()
-    }
-}
+mk_id!(UnitId and UnitIdStub);
diff --git a/crates/rocie-server/src/storage/sql/unit_property.rs b/crates/rocie-server/src/storage/sql/unit_property.rs
new file mode 100644
index 0000000..9da2d2e
--- /dev/null
+++ b/crates/rocie-server/src/storage/sql/unit_property.rs
@@ -0,0 +1,23 @@
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+
+use crate::storage::sql::{mk_id, unit::UnitId};
+
+/// An unit property describes a property that can be measured by units.
+/// For example velocity, mass or volume.
+#[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
+pub(crate) struct UnitProperty {
+    /// The unique ID for this unit property.
+    pub(crate) id: UnitPropertyId,
+
+    /// The user-displayed name of this property.
+    pub(crate) name: String,
+
+    /// The units with are measuring this property.
+    pub(crate) units: Vec<UnitId>,
+
+    /// An description of this property.
+    pub(crate) description: Option<String>,
+}
+
+mk_id!(UnitPropertyId and UnitPropertyIdStub);