diff --git a/pilab/crud/Cube.py b/pilab/crud/Cube.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bae17a0624ae5da75eec9ebba897bda66e738a1
--- /dev/null
+++ b/pilab/crud/Cube.py
@@ -0,0 +1,94 @@
+import logging as log
+from typing import List
+from datetime import datetime, timezone
+from zoneinfo import ZoneInfo
+from pydantic import BaseModel
+from sqlalchemy.orm import Session
+from pilab.crud.Util import from_json, update_attrs
+
+
+from pilab.schemas.Cube import CubesType
+from pilab.schemas.Pi import PiType
+from pilab.crud.Hardware import Controller, Switch
+from pilab.crud.Pi import Pi
+from pilab.events import meta
+
+log.getLogger('sqlalchemy.engine').setLevel(log.WARNING)
+
+
+
+class Cube(object):
+
+    @staticmethod#DONE
+    def get(db: Session, cube_id: int = None, switch_id: int = None, controller_id : int = None):
+        if cube_id:
+            db_cube = db.query(CubesType).filter(CubesType.id == cube_id).first()
+        elif switch_id:
+            db_cube = db.query(CubesType).filter(CubesType.switch_id == switch_id).first()
+        elif controller_id:
+            db_cube = db.query(CubesType).filter(CubesType.controller_id == controller_id).first()
+            
+        controller = Controller.get(db, host_id = db_cube.controller_id) if db_cube.controller_id else None
+        switch = Switch.get(db, host_id = db_cube.switch_id)
+        pis = Pi.getAll(db, cube_id = db_cube.id)
+        return meta.Cube(
+            id=db_cube.id,
+            controller=controller,
+            switch=switch,
+            pis=pis,
+        )
+
+    @staticmethod#DONE
+    def create(db: Session, cube: meta.Cube):
+        if cube.controller:
+            controller_id = Controller.create(db, cube.controller).id
+        else:
+            controller_id = None
+        switch_id = Switch.create(db, cube.switch).id
+        db_cube = CubesType(
+            id=cube.id,
+            controller_id=controller_id,
+            switch_id=switch_id,
+        )
+        db.add(db_cube)
+        db.flush()
+        for pi in cube.pis:
+            Pi.create(db, pi)
+        return db_cube.id
+
+    @staticmethod#DONE
+    def delete(db: Session, cube_id: int):
+        db_cube = db.query(CubesType).filter(CubesType.id == cube_id).first()
+        db_pis = db.query(PiType).filter(
+            PiType.cube_id == cube_id).all()
+        for db_pi in db_pis:
+            Pi.delete(db, db_pi.host_id)
+        db.delete(db_cube)
+        if db_cube.controller_id:
+            Controller.delete(db, db_cube.controller_id)
+        Switch.delete(db, db_cube.switch_id)
+        db.flush()
+
+    @staticmethod
+    def get_ids(db: Session):
+        ids = []
+        db_ids = db.query(CubesType.id).all()
+        for i, in db_ids:
+            ids.append(i)
+        return ids
+
+    @staticmethod
+    def is_valid_id(db: Session, cube_id: int):
+        cube = db.query(CubesType).filter(
+            CubesType.id == cube_id).first()
+        return True if cube else False
+
+    @staticmethod
+    def get_all(db: Session):
+        ids = Cube.get_ids(db)
+        cubes = []
+        for i in ids:
+            cube = Cube.get(db, i)
+            cubes.append(cube)
+        return cubes
+
diff --git a/pilab/crud/Hardware.py b/pilab/crud/Hardware.py
new file mode 100644
index 0000000000000000000000000000000000000000..c949203325a45afdd70efc693bdf224716e9ee6f
--- /dev/null
+++ b/pilab/crud/Hardware.py
@@ -0,0 +1,132 @@
+import logging as log
+from sqlalchemy.orm import Session
+from pilab.crud.Util import from_json, update_attrs
+
+from pilab.crud.Host import Host
+from pilab.schemas.Cube import CubesType
+from pilab.schemas.Pi import PiType
+from pilab.schemas.Hardware import SwitchType, ControllerType
+from pilab.events import meta
+
+log.getLogger('sqlalchemy.engine').setLevel(log.WARNING)
+
+class Switch(object):
+    @staticmethod
+    def create(db: Session, switch: meta.Switch):#DONE
+        host = Host.create(db, switch)
+        db_switch = from_json(SwitchType, switch.dict(), host_id=host.id)
+        db.add(db_switch)
+        db.flush()
+        switch = meta.Switch(**vars(db_switch), **host.dict())
+        return switch
+
+    @staticmethod
+    def get(db: Session, host_id: int = None, cube_id: int = None, pi_id: int = None):
+        if host_id:
+            db_switch = db.query(SwitchType).filter(
+                SwitchType.host_id == host_id
+            ).first()
+        elif cube_id:
+            db_cube = db.query(CubesType).filter(
+                CubesType.switch_id == host_id
+            ).first()
+            db_switch = db.query(SwitchType).filter(
+                SwitchType.host_id == db_cube.switch_id
+            ).first()
+        elif pi_id:
+            db_pi= db.query(PiType).filter(
+                PiType.host_id == pi_id
+            ).first()
+            db_cube = db.query(CubesType).filter(
+                CubesType.id == db_pi.cube_id
+            ).first()
+            db_switch = db.query(SwitchType).filter(
+                SwitchType.host_id == db_cube.switch_id
+            ).first()
+        
+        host = Host.get(db, db_switch.host_id)
+        return meta.Switch(**vars(db_switch), **host.dict())
+
+    @staticmethod#DONE
+    def delete(db: Session, host_id: int):
+        db_switch = db.query(SwitchType).filter(
+            SwitchType.host_id == host_id
+        ).first()
+        db.delete(db_switch)
+        db.flush()
+        Host.delete(db, host_id)
+
+    @staticmethod
+    def update(db: Session, switch: meta.HostUpdate, _id: int):
+        host = Host.update(db, switch, _id)
+        return host
+
+    @staticmethod
+    def getAll(db: Session):
+        switches = []
+        db_switches = db.query(SwitchType).all()
+        for db_switch in db_switches:
+            host = Host.get(db, db_switch.host_id)
+            switches.append(meta.Switch(**vars(db_switch), **host.dict()))
+        return switches
+
+
+class Controller(object):
+    @staticmethod
+    def create(db: Session, controller: meta.Controller):
+        host = Host.create(db, controller)
+        db_controller = from_json(ControllerType, controller.dict(), host_id=host.id)
+        db.add(db_controller)
+        db.flush()
+        controller = meta.Controller(**vars(db_controller), **host.dict())
+        return controller
+
+    @staticmethod#DONE
+    def delete(db: Session, host_id: int):
+        db_controller = db.query(ControllerType).filter(
+            ControllerType.host_id == host_id
+        ).first()
+        db.delete(db_controller)
+        db.flush()
+        Host.delete(db, host_id)
+
+    @staticmethod
+    def get(db: Session, host_id: int = None, cube_id: int = None, pi_id: int = None):
+        if host_id:
+            db_controller = db.query(ControllerType).filter(
+                ControllerType.host_id == host_id
+            ).first()
+        elif cube_id:
+            db_cube = db.query(CubesType).filter(
+                CubesType.controller_id == host_id
+            ).first()
+            db_controller = db.query(ControllerType).filter(
+                ControllerType.host_id == db_cube.controller_id
+            ).first()
+        elif pi_id:
+            db_pi= db.query(PiType).filter(
+                PiType.host_id == pi_id
+            ).first()
+            db_cube = db.query(CubesType).filter(
+                CubesType.id == db_pi.cube_id
+            ).first()
+            db_controller= db.query(ControllerType).filter(
+                ControllerType.host_id == db_cube.controller_id
+            ).first()
+        
+        host = Host.get(db, db_controller.host_id)
+        return meta.Controller(**vars(db_controller), **host.dict())
+
+    @staticmethod
+    def update(db: Session, controller: meta.HostUpdate, _id: int):
+        host = Host.update(db, controller, _id)
+        return host
+
+    @staticmethod
+    def getAll(db: Session):
+        controllers = []
+        db_controllers = db.query(ControllerType).all()
+        for db_controller in db_controllers:
+            host = Host.get(db, db_controller.host_id)
+            controllers.append(meta.Controller(**vars(db_controller), **host.dict()))
+        return controllers
\ No newline at end of file
diff --git a/pilab/crud/Host.py b/pilab/crud/Host.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b01ddb632da6966ec118f84756f7ff12edda943
--- /dev/null
+++ b/pilab/crud/Host.py
@@ -0,0 +1,49 @@
+import logging as log
+from typing import List
+from datetime import datetime, timezone
+from zoneinfo import ZoneInfo
+from pydantic import BaseModel
+from sqlalchemy import inspect
+from sqlalchemy.orm import Session, class_mapper
+from pilab.crud.Util import from_json, update_attrs
+from pilab.schemas.Host import HostType 
+from pilab.events import meta
+log.getLogger('sqlalchemy.engine').setLevel(log.WARNING)
+
+class Host(object):
+    @staticmethod
+    def create(db: Session, host: meta.Host):#DONE
+        db_host = from_json(HostType, host.dict())
+        db.add(db_host)
+        db.flush()
+        resp_host = meta.Host(**vars(db_host))
+        return resp_host
+
+    @staticmethod
+    def update(db: Session, host: meta.HostUpdate, host_id: int):
+        db_host = db.query(HostType).filter(
+            HostType.id == host_id).first()
+        update_attrs(host, db_host)
+        db.flush()
+        resp_host = meta.Host(**vars(db_host))
+        return resp_host
+
+    @staticmethod
+    def get(db: Session, id: int):
+        db_host = db.query(HostType).filter(
+            HostType.id == id).first()
+        return meta.Host(**vars(db_host))
+
+    @staticmethod
+    def delete(db: Session, host_id: int):#DONE
+        db_host = db.query(HostType).filter(
+            HostType.id == host_id).first()
+        db.delete(db_host)
+        db.flush()
+        return
+
+    @staticmethod
+    def is_valid_id(db: Session, host_id: int):
+        pi = db.query(HostType).filter(
+            HostType.host_id == host_id).first()
+        return True if pi else False
\ No newline at end of file
diff --git a/pilab/crud/Image.py b/pilab/crud/Image.py
new file mode 100644
index 0000000000000000000000000000000000000000..f45d76041e2b3df33bb9bc0d3c685e9216e25f52
--- /dev/null
+++ b/pilab/crud/Image.py
@@ -0,0 +1,212 @@
+import logging as log
+from typing import List
+from datetime import datetime, timezone
+from zoneinfo import ZoneInfo
+from pydantic import BaseModel
+from sqlalchemy import inspect
+from sqlalchemy.orm import Session, class_mapper
+from pilab.crud.Util import from_json, update_attrs
+
+
+
+from pilab.schemas.Image import ImageType, ScriptType, UserDataType
+from pilab.events import meta
+
+log.getLogger('sqlalchemy.engine').setLevel(log.WARNING)
+
+
+class Image(object):
+    @staticmethod
+    def create(db: Session, **kwargs):
+        db_image = from_json(ImageType, kwargs, change=datetime.now(timezone.utc))
+        db.add(db_image)
+        db.flush()
+        return db_image.id
+
+    @staticmethod
+    def delete(db: Session, image_id: int):
+        db_image = db.query(ImageType).filter(
+            ImageType.id == image_id
+        ).first()
+        if db_image:
+            db.delete(db_image)
+        db.flush()
+
+    @staticmethod
+    def get(db: Session, _id: int = None, name: str = None, version: str = None, username: str = None):
+        """Returns images from the postgres database. List or single image.
+        filter by id to get specific image.
+        filter by name to get all images with name
+        filter by name and version to get specific image. Version without name will be omitted.
+        filter by username to get all public images and user specific images. Get all images if None
+        """
+
+        def process_images(data):
+            if isinstance(data, list):
+                images = []
+                for image in data or []:
+                    image.change = image.change.replace(tzinfo=ZoneInfo('UTC'))
+                    if username is None or image.public or image.owner == username:
+                        images.append(meta.Image(
+                        **vars(image)))
+                return images
+            else:
+                image = data
+                if image:
+                    image.change = image.change.replace(tzinfo=ZoneInfo('UTC'))
+                    return meta.Image(
+                        **vars(image)) if username is None or image.public or data.owner == username else None
+                else:
+                    return None
+
+        if _id:
+            db_image = db.query(ImageType).filter(ImageType.id == _id).first()
+            return process_images(db_image)
+        elif name and not version:
+            db_images = db.query(ImageType).filter(ImageType.name == name).all()
+            return process_images(db_images)
+        elif name and version:
+            db_image = db.query(ImageType).filter(
+                ImageType.name == name,
+                ImageType.version == version
+            ).first()
+            return process_images(db_image)
+        else:
+            db_images = db.query(ImageType).all()
+            return process_images(db_images)
+
+    @staticmethod
+    def update_size(db: Session, image_id: int, size: int):
+        db_image = db.query(ImageType).filter(
+            ImageType.id == image_id
+        ).first()
+        db_image.size = size
+        db_image.change = datetime.now(timezone.utc)
+        db.flush()
+        return
+
+    @staticmethod
+    def update_script(db: Session, image_id: int, script_id: int):
+        db_image = db.query(ImageType).filter(
+            ImageType.id == image_id
+        ).first()
+        db_image.script_id = script_id
+        db_image.change = datetime.now(timezone.utc)
+        db.flush()
+        return
+
+    @staticmethod
+    def get_id(db: Session, name: str, version: str):
+        db_image = db.query(ImageType).filter(
+            ImageType.name == name,
+            ImageType.version == version
+        ).first()
+        if db_image:
+            return db_image.id
+        else:
+            return None
+
+    @staticmethod
+    def is_valid_id(db: Session, image_id: int):
+        cube = db.query(ImageType).filter(
+            ImageType.id == image_id).first()
+        return True if cube else False
+
+class UserData(object):
+    @staticmethod
+    def create(db: Session, data: str, owner: str):
+        db_data = UserDataType(
+            data=data,
+            owner=owner
+        )
+        db.add(db_data)
+        db.flush()
+        return meta.UserData(**vars(db_data))
+
+    @staticmethod
+    def update(db: Session, data: str, _id: int):
+        db_data = db.query(UserDataType).filter(
+            UserDataType.id == _id).first()
+        if db_data:
+            db_data.data = data
+        db.flush()
+        return meta.UserData(**vars(db_data))
+
+    @staticmethod
+    def delete(db: Session, _id: int):
+        db_data = db.query(UserDataType).filter(UserDataType.id == _id).first()
+        if db_data:
+            db.delete(db_data)
+        db.flush()
+
+    @staticmethod
+    def get(db: Session, _id: int = None, owner: str = None):
+        if _id:
+            db_data = db.query(UserDataType).filter(UserDataType.id == _id).first()
+            return meta.UserData(**vars(db_data))
+        elif owner:
+            db_datas = db.query(UserDataType).filter(UserDataType.owner == owner).all()
+            return [meta.UserData(**vars(data)) for data in db_datas]
+        else:
+            db_datas = db.query(UserDataType).all()
+            return [meta.UserData(**vars(data)) for data in db_datas]
+
+    @staticmethod
+    def is_valid_id(db: Session, _id: int):
+        db_data = db.query(UserDataType).filter(
+            UserDataType.id == _id).first()
+        return True if db_data else False
+
+
+class Script(object):
+    @staticmethod
+    def create(db: Session, script: str, script_chroot: str, owner: str, name: str, read_only: bool):
+        db_script = ScriptType(
+            owner=owner,
+            name=name,
+            read_only=read_only,
+            script=script,
+            script_chroot=script_chroot
+        )
+        db.add(db_script)
+        db.flush()
+        return meta.Script(**vars(db_script))
+
+    @staticmethod
+    def update(db: Session, script_id: int, script: str = None, script_chroot: str = None, name: str = None):
+        db_script = db.query(ScriptType).filter(ScriptType.id == script_id).first()
+        if script:
+            db_script.script = script
+        if script_chroot:
+            db_script.script_chroot = script_chroot
+        if name:
+            db_script.name = name
+        db.flush()
+        return meta.Script(**vars(db_script))
+
+    @staticmethod
+    def delete(db: Session, script_id: int):
+        db_script = db.query(ScriptType).filter(
+            ScriptType.id == script_id
+        ).first()
+        if db_script:
+            db.delete(db_script)
+        db.flush()
+
+    @staticmethod
+    def get(db: Session, script_id: int = None, name: str = None):
+        if script_id:
+            db_script = db.query(ScriptType).filter(ScriptType.id == script_id).first()
+            return meta.Script(**vars(db_script)) if db_script else None
+        elif name:
+            db_script = db.query(ScriptType).filter(ScriptType.name == name).first()
+            return meta.Script(**vars(db_script)) if db_script else None
+        else:
+            db_scripts = db.query(ScriptType).all()
+            return [meta.Script(**vars(p)) for p in db_scripts]
+
+    @staticmethod
+    def is_valid_id(db: Session, script_id: int):
+        script = db.query(ScriptType).filter(
+            ScriptType.id == script_id).first()
+        return True if script else False
\ No newline at end of file
diff --git a/pilab/crud/Key.py b/pilab/crud/Key.py
new file mode 100644
index 0000000000000000000000000000000000000000..6adaf0434e688e5329a3abff895369bc2901a83c
--- /dev/null
+++ b/pilab/crud/Key.py
@@ -0,0 +1,50 @@
+import logging as log
+from typing import List
+from datetime import datetime, timezone
+from zoneinfo import ZoneInfo
+from pydantic import BaseModel
+from sqlalchemy import inspect
+from sqlalchemy.orm import Session, class_mapper
+from pilab.crud.Util import from_json, update_attrs
+
+
+from pilab.schemas.Key import KeyType
+from pilab.events import meta
+
+log.getLogger('sqlalchemy.engine').setLevel(log.WARNING)
+
+
+class Key(object):
+    @staticmethod
+    def create(db: Session, **kwargs):
+        db_key = from_json(KeyType, kwargs)
+        db.add(db_key)
+        db.flush()
+        return db_key.id
+
+    @staticmethod
+    def delete(db: Session, key_id: str):#DONE
+        db_key = db.query(KeyType).filter(
+            KeyType.id == key_id
+        ).first()
+        db.delete(db_key)
+        db.flush()
+        return
+    
+    @staticmethod
+    def get(db: Session, id: str):#DONE
+        db_key = db.query(KeyType).filter(KeyType.id == id).first()
+        return meta.Key(**vars(db_key))
+
+    @staticmethod
+    def getAll(db: Session, owner: str = None):#DONE
+        if owner:
+            db_keys = db.query(KeyType).filter(KeyType.owner == owner).all()
+        else:
+            db_keys = db.query(KeyType).all()
+
+        keys = []
+        for db_key in db_keys:
+            key = meta.Key(**vars(db_key))
+            keys.append(key)
+        return keys
\ No newline at end of file
diff --git a/pilab/crud/Pi.py b/pilab/crud/Pi.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb4d9b30984e87a07059a5b06fc082727abe12d4
--- /dev/null
+++ b/pilab/crud/Pi.py
@@ -0,0 +1,147 @@
+import logging as log
+from typing import List
+from sqlalchemy.orm import Session
+from pilab.crud.Util import from_json, update_attrs
+
+from pilab.crud.Host import Host
+from pilab.crud.Pi import Pi
+from pilab.schemas.Host import HostType
+from pilab.schemas.Pi import PiType
+from pilab.schemas.Host import HostType
+from pilab.schemas.Cube import CubesType
+from pilab.events import meta
+
+log.getLogger('sqlalchemy.engine').setLevel(log.WARNING)
+
+class Pi(object):
+    @staticmethod
+    def create(db: Session, pi: meta.Pi):#DONE
+        host = Host.create(db, pi)
+        db_pi = from_json(PiType, pi.dict(), host_id=host.id)
+        db.add(db_pi)
+        db.flush()
+        pi = meta.Pi(**vars(db_pi), **host.dict())
+        return pi
+
+    @staticmethod
+    def update(db: Session, pi: meta.PiUpdate, pi_id: int):
+        db_pi = db.query(PiType).filter(
+            PiType.host_id == pi_id).first()
+        host = Host.update(db, pi, db_pi.host_id)
+        update_attrs(pi, db_pi)
+        db.flush()
+        pi = meta.Pi(**vars(db_pi), **host.dict())
+        return pi
+
+    @staticmethod
+    def update_image(db: Session, pi_id: int, image_id: int, user_data_id: int):
+        db_pi = db.query(PiType).filter(
+            PiType.host_id == pi_id).first()
+        db_pi.image_id = image_id
+        db_pi.user_data_id = user_data_id
+        db.flush()
+
+    @staticmethod
+    def update_playbook(db: Session, pi_id: int, playbook_id: int):
+        db_pi = db.query(PiType).filter(
+            PiType.host_id == pi_id).first()
+        db_pi.playbook_id = playbook_id
+        db.flush()
+ 
+    @staticmethod
+    def assign_image(db: Session, pi_id: int, image_id: int):
+        db_pi = db.query(PiType).filter(
+            PiType.host_id == pi_id).first()
+        db_pi.image_id = image_id
+        db.flush()
+    
+    @staticmethod
+    def assign_host_key(db: Session, pi_id: int, key_id: int):
+        db_pi = db.query(PiType).filter(
+            PiType.host_id == pi_id).first()
+        db_pi.key_id = key_id
+        db.flush()
+
+    @staticmethod
+    def assign_data(db: Session, pi_id: int, user_data_id: int):
+        db_pi = db.query(PiType).filter(
+            PiType.host_id == pi_id).first()
+        db_pi.user_data_id = user_data_id
+        db.flush()
+
+    @staticmethod
+    def get(db: Session, id: int = None, serial : bytes = None, mac: int = None):#DONE
+        if id:
+            db_pi = db.query(PiType).filter(PiType.host_id == id).first()
+        elif serial:
+            db_pi = db.query(PiType).filter(PiType.serial == serial).first()
+        elif mac:
+            db_host = db.query(HostType).filter(HostType.mac == mac).first()
+            db_pi = db.query(PiType).filter(PiType.host_id == db_host.id).first()
+        host = Host.get(db, db_pi.host_id)
+        return meta.Pi(**vars(db_pi), **host.dict())
+
+    @staticmethod
+    def delete(db: Session, host_id: int):#DONE
+        db_pi = db.query(PiType).filter(
+            PiType.host_id == host_id
+        ).first()
+        db.delete(db_pi)
+        db.flush()
+        Host.delete(db, db_pi.host_id)
+        return
+
+    @staticmethod
+    def is_valid_id(db: Session, host_id: int):
+        pi = db.query(PiType).filter(
+            PiType.host_id == host_id).first()
+        return True if pi else False
+
+    @staticmethod
+    def get_cube_id(db: Session, pi_id: int):
+        pi = Pi.get(db, pi_id)
+        cube_id = db.query(CubesType.id).filter(
+            CubesType.id == pi.cube_id).first()
+        return cube_id
+
+    @staticmethod
+    def getAll(db: Session, _ids: List[int] = None, cube_id: int = None, switch_id: int = None, controller_id: int = None, image_id: int= None):#DONE
+        if _ids:
+            ids = db.query(PiType.host_id).filter(PiType.host_id.in_(_ids)).all()
+            if len(ids) != len(_ids):
+                invalid_ids = set(ids) - {pi.id for pi in ids}
+                raise ValueError(f"Invalid ID(s) in the list: {invalid_ids}")
+        elif cube_id:
+            ids = db.query(PiType.host_id).filter(
+            PiType.cube_id == cube_id).all()
+        elif switch_id:
+            db_cube = db.query(CubesType).filter(
+                CubesType.switch_id == switch_id
+            ).first()
+            ids = db.query(PiType.host_id).filter(
+                PiType.cube_id == db_cube.id
+            ).all()
+        elif controller_id:
+            db_cube = db.query(CubesType).filter(
+                CubesType.controller_id == controller_id
+            ).first()
+            ids = db.query(PiType.host_id).filter(
+                PiType.cube_id == db_cube.id
+            ).all()
+        elif image_id:
+            ids = db.query(PiType.host_id).filter(PiType.image_id == image_id).all()
+        else:
+            ids = db.query(PiType.host_id).all()
+        
+        pis = []
+        for i, in ids:
+            pi = Pi.get(db, i)
+            pis.append(pi)
+        return pis
+    @staticmethod
+    def get_ids(db: Session):#DONE
+        ids_tuple = db.query(PiType.host_id).all()
+        ids = []
+        for id, in ids_tuple:
+            ids.append(id)
+        return ids
\ No newline at end of file
diff --git a/pilab/crud/Util.py b/pilab/crud/Util.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c8fe254c34f52fec6b4baf0bf2349e0a74ebfe8
--- /dev/null
+++ b/pilab/crud/Util.py
@@ -0,0 +1,41 @@
+import logging as log
+from pydantic import BaseModel
+from sqlalchemy import inspect
+from sqlalchemy.orm import class_mapper
+from pilab.schemas.Util import OffsetType
+from sqlalchemy.orm import Session
+
+log.getLogger('sqlalchemy.engine').setLevel(log.WARNING)
+
+def from_json(model, data, **kwargs):
+    mapper = class_mapper(model)
+    keys = mapper.attrs.keys()
+    relationships = inspect(mapper).relationships
+    args = {k: v for k, v in data.items()
+            if k in keys and k not in relationships}
+    return model(**args, **kwargs)
+
+
+def update_attrs(data: BaseModel, db_data):
+    for k, v in data.dict().items():
+        mapper = class_mapper(type(db_data))
+        if v is not None and k in mapper.attrs.keys():
+            setattr(db_data, k, v)
+
+class Offset(object):
+    @staticmethod
+    def set(db: Session, stream_name: str, offset: int):
+        db_offset = db.query(OffsetType).filter(
+            OffsetType.stream_name == stream_name).first()
+        if not db_offset:
+            db_offset = OffsetType(stream_name=stream_name, stream_offset=offset)
+            db.add(db_offset)
+        else:
+            db_offset.stream_offset = offset
+        db.flush()
+
+    @staticmethod
+    def get(db: Session, stream_name:str):
+        db_offset = db.query(OffsetType).filter(
+            OffsetType.stream_name == stream_name).first()
+        return db_offset.stream_offset if db_offset else None
\ No newline at end of file
diff --git a/pilab/crud/__init__.py b/pilab/crud/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pilab/database.py b/pilab/database.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c5a85968e755655ee6775cd67e0fe4a8ee9e7fe
--- /dev/null
+++ b/pilab/database.py
@@ -0,0 +1,99 @@
+import binascii
+import os
+import socket
+import struct
+import logging
+from contextlib import contextmanager
+from sqlalchemy import create_engine
+from sqlalchemy import types, text
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+import ipaddress
+
+logger = logging.getLogger(__name__)
+
+DB_ADDRESS = os.getenv('DB_ADDRESS', '127.0.0.1')
+DB_DATABASE = os.getenv('DB_DATABASE')
+DB_PASSWORD = os.getenv('DB_PASSWORD')
+DB_USER = os.getenv('DB_USER')
+SQLALCHEMY_DATABASE_URL = 'postgresql://' + DB_USER + ':' + DB_PASSWORD + '@' + DB_ADDRESS + '/' + DB_DATABASE
+
+engine = create_engine(SQLALCHEMY_DATABASE_URL)
+
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+Base = declarative_base()
+
+
+@contextmanager
+def get_db_context():
+    """Provide a transactional scope around a series of operations."""
+    db = SessionLocal()
+    try:
+        yield db
+        db.commit()
+    except:
+        db.rollback()
+        raise
+    finally:
+        db.close()
+
+
+def get_db():
+    with get_db_context() as db:
+        yield db
+
+
+
+def init_database():
+    try:
+        with get_db_context() as db:
+            with open("./init.sql") as file:
+                query = text(file.read())
+                db.execute(query)
+    except Exception as e:
+        logger.error(f"Failed to initialize database schema; message: {e}")
+        raise
+
+
+class MAC(types.TypeDecorator):
+    impl = types.VARBINARY
+
+    def process_result_value(self, value, dialect):
+        if value is None:
+            return value
+        return binascii.hexlify(value, ':')
+
+    def process_bind_param(self, value, dialect):
+        if value is None:
+            return value
+        return binascii.unhexlify(value.replace(':', ''))
+
+
+class SERIAL(types.TypeDecorator):
+    impl = types.VARBINARY
+
+    def process_result_value(self, value, dialect):
+        if value is None:
+            return value
+        r = bytes(value.hex(), 'utf-8')
+        return r
+
+    def process_bind_param(self, value, dialect):
+        if value is None:
+            return value
+        return bytes.fromhex(value.decode())
+
+
+class IP(types.TypeDecorator):
+    impl = types.BIGINT
+
+    def process_result_value(self, value, dialect):
+        if value is None:
+            return value
+        return ipaddress.IPv4Address(socket.inet_ntoa(struct.pack('!L', value)))
+
+    def process_bind_param(self, value, dialect):
+        if value is None:
+            return value
+        packed_ip = socket.inet_aton(str(value))
+        return struct.unpack("!L", packed_ip)[0]
diff --git a/pilab/events/meta.py b/pilab/events/meta.py
index b65d8f2d261ba2ec2307d8313157854f44fc651a..72611561d4e7171552999288a9c755c3edb5e283 100644
--- a/pilab/events/meta.py
+++ b/pilab/events/meta.py
@@ -53,7 +53,7 @@ class HostUpdate(Host):
     __annotations__ = convert_to_optional(Host)
 
 
-class Pi(Host):
+class Pi_old(Host):
     """
     representation of pi object with host id
     """
@@ -61,6 +61,17 @@ class Pi(Host):
     position: int
     ssh_host_ed25519_key: Optional[str]
 
+class Pi(Host):
+    """
+    representation of pi object with host id
+    """
+    serial: bytes
+    position: int
+    key_id: Optional[int]
+    image_id: Optional[int]
+    user_data_id: Optional[int]
+    playbook_id: Optional[int]
+    cube_id: int
 
 class PiUpdate(HostUpdate):
     """
@@ -78,7 +89,46 @@ class Cube(BaseModel):
     switch: Switch
     pis: List[Pi]
 
+class UserData(BaseModel):
+    id: int
+    owner: str
+    data: str
+
+class Image(BaseModel):
+    id: int
+    name: str
+    version: str
+    owner: Optional[str]
+    size: Optional[int]
+    public: bool
+    cloud_init: bool
+    script_id: Optional[int]
+    change: datetime
+
+class Script(BaseModel):
+    id: int
+    name: str
+    owner: str
+    read_only: bool
+    script: str
+    script_chroot: str
+
 
+class Key(BaseModel):
+    """
+    representation of key object
+    """
+    id: int
+    host_key: str
+    owner: str
+
+class KeyBinding(BaseModel):
+    """
+    representation of key_binding object
+    """
+    pi_id: int
+    key_id: str
+    
 class EventType(str, Enum):
     CREATE = 'create'
     UPDATE = 'update'
diff --git a/pilab/schemas/Cube.py b/pilab/schemas/Cube.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f9503185a83cb22be02d3b9276345a115f2a73c
--- /dev/null
+++ b/pilab/schemas/Cube.py
@@ -0,0 +1,9 @@
+from sqlalchemy import Column, Integer, ForeignKey
+
+from pilab.database import Base, MAC, SERIAL, IP
+
+class CubesType(Base):
+    __tablename__ = "cubes"
+    id = Column(Integer, primary_key=True)
+    switch_id = Column(Integer, ForeignKey('hosts.id'))
+    controller_id = Column(Integer, ForeignKey('hosts.id'))
\ No newline at end of file
diff --git a/pilab/schemas/Hardware.py b/pilab/schemas/Hardware.py
new file mode 100644
index 0000000000000000000000000000000000000000..443c23a19dd9a15b7e1c78588e2e2fd6def2a30e
--- /dev/null
+++ b/pilab/schemas/Hardware.py
@@ -0,0 +1,13 @@
+from sqlalchemy import Column, Integer, ForeignKey, Boolean
+
+from pilab.database import Base
+
+class SwitchType(Base):
+    __tablename__ = "switches"
+    host_id = Column(Integer, ForeignKey('hosts.id'), primary_key=True)
+
+    poe = Column(Boolean)
+
+class ControllerType(Base):
+    __tablename__ = "controllers"
+    host_id = Column(Integer, ForeignKey('hosts.id'), primary_key=True)
\ No newline at end of file
diff --git a/pilab/schemas/Host.py b/pilab/schemas/Host.py
new file mode 100644
index 0000000000000000000000000000000000000000..6febda557d7202202887cf8a2a33d603d0fc901b
--- /dev/null
+++ b/pilab/schemas/Host.py
@@ -0,0 +1,11 @@
+from sqlalchemy import Column, Integer, ForeignKey, String, Boolean, BigInteger, DateTime
+
+from pilab.database import Base, MAC, SERIAL, IP
+
+class HostType(Base):
+    __tablename__ = "hosts"
+    id = Column(Integer, primary_key=True)
+    mac = Column(MAC())
+    hostname = Column(String)
+    ipv4_address = Column(IP())
+    model = Column(String)
\ No newline at end of file
diff --git a/pilab/schemas/Image.py b/pilab/schemas/Image.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9f8ae66e97488482d1724dd6a8a222f72de2656
--- /dev/null
+++ b/pilab/schemas/Image.py
@@ -0,0 +1,32 @@
+from sqlalchemy import Column, Integer, ForeignKey, String, Boolean, BigInteger, DateTime
+
+from pilab.database import Base, MAC, SERIAL, IP
+
+
+
+class ScriptType(Base):
+    __tablename__ = "scripts"
+    id = Column(Integer, primary_key=True)
+    name = Column(String)
+    owner = Column(String)
+    read_only = Column(Boolean)
+    script = Column(String)
+    script_chroot = Column(String)
+
+class ImageType(Base):
+    __tablename__ = "images"
+    id = Column(Integer, primary_key=True)
+    name = Column(String)
+    version = Column(String)
+    owner = Column(String)
+    size = Column(Integer)
+    public = Column(Boolean)
+    cloud_init = Column(Boolean)
+    change = Column(DateTime)
+    script_id = Column(Integer, ForeignKey('scripts.id'))
+
+class UserDataType(Base):
+    __tablename__ = "user_data"
+    id = Column(Integer, primary_key=True)
+    owner = Column(String)
+    data = Column(String)
\ No newline at end of file
diff --git a/pilab/schemas/Key.py b/pilab/schemas/Key.py
new file mode 100644
index 0000000000000000000000000000000000000000..35e305353c2806ce8e7e8164fb244af72f8fedc6
--- /dev/null
+++ b/pilab/schemas/Key.py
@@ -0,0 +1,11 @@
+from sqlalchemy import Column, Integer, String
+
+from pilab.database import Base, MAC, SERIAL, IP
+
+
+
+class KeyType(Base):
+    __tablename__ = "keys"
+    id = Column(Integer, primary_key=True)
+    owner = Column(String)
+    host_key = Column(String)
diff --git a/pilab/schemas/Pi.py b/pilab/schemas/Pi.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b5bdb3877ea549bc49822e1354df7a2c32f4695
--- /dev/null
+++ b/pilab/schemas/Pi.py
@@ -0,0 +1,15 @@
+from sqlalchemy import Column, Integer, ForeignKey
+
+from pilab.database import Base, MAC, SERIAL, IP
+
+
+class PiType(Base):
+    __tablename__ = "pis"
+    host_id = Column(Integer, ForeignKey('hosts.id'), primary_key=True)
+    cube_id = Column(Integer, ForeignKey('cubes.id'), primary_key=True)
+    serial = Column(SERIAL())
+    position = Column(Integer)
+    user_data_id = Column(Integer, ForeignKey('user_data.id'))
+    image_id = Column(Integer, ForeignKey('images.id'))
+    playbook_id = Column(Integer)
+    key_id = Column(Integer, ForeignKey('keys.id'))
\ No newline at end of file
diff --git a/pilab/schemas/Util.py b/pilab/schemas/Util.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e09d5d8898ef1863ba8e25f9df83d465db7cb8a
--- /dev/null
+++ b/pilab/schemas/Util.py
@@ -0,0 +1,8 @@
+from sqlalchemy import Column, String, BigInteger
+
+from pilab.database import Base
+
+class OffsetType(Base):
+    __tablename__ = "stream_offsets"
+    stream_name = Column(String, primary_key=True)
+    stream_offset = Column(BigInteger)
\ No newline at end of file
diff --git a/pilab/schemas/__init__.py b/pilab/schemas/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/setup.py b/setup.py
index 17257795335a85d2dff570a7e44d6340ef9170a9..f6a335bbc6aebd1c3397bce28ea1465dabb1d589 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='pilab',
-    version='4.3.1',
+    version='5.0.0',
     description='Shared-Libs for the pi-lab microservices',
     url='https://code.fbi.h-da.de/api/v4/projects/27896/packages/pypi/pilab',
     author='Max Reinheimer',