From c43b17662df38855e5f503da6e6a74faa4e91f92 Mon Sep 17 00:00:00 2001 From: Conrad Nelson Date: Wed, 3 Sep 2025 10:55:25 -0500 Subject: [PATCH] Building new test application. --- crudkit/core/base.py | 15 ++++++++++++++- crudkit/core/service.py | 30 +++++++++++++++++++++++++++--- muck/models/dbref.py | 21 ++++++++++++++++++++- muck/models/exit.py | 6 ++++++ muck/models/player.py | 6 ++++++ muck/models/program.py | 6 ++++++ muck/models/room.py | 6 ++++++ muck/models/thing.py | 6 ++++++ 8 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 muck/models/exit.py create mode 100644 muck/models/player.py create mode 100644 muck/models/program.py create mode 100644 muck/models/room.py create mode 100644 muck/models/thing.py diff --git a/crudkit/core/base.py b/crudkit/core/base.py index 3492dcb..e74fef0 100644 --- a/crudkit/core/base.py +++ b/crudkit/core/base.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, DateTime, Boolean, func +from sqlalchemy import Column, Integer, DateTime, Boolean, String, JSON, func from sqlalchemy.orm import declarative_mixin, declarative_base Base = declarative_base() @@ -11,3 +11,16 @@ class CRUDMixin: def as_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} + +class Version(Base): + __tablename__ = "versions" + + id = Column(Integer, primary_key=True) + model_name = Column(String, nullable=False) + object_id = Column(Integer, nullable=False) + change_type = Column(String, nullable=False) + data = Column(JSON, nullable=True) + timestamp = Column(DateTime, default=func.now()) + + actor = Column(String, nullable=True) + metadata = Column(JSON, nullable=True) diff --git a/crudkit/core/service.py b/crudkit/core/service.py index b240a60..c235213 100644 --- a/crudkit/core/service.py +++ b/crudkit/core/service.py @@ -1,5 +1,6 @@ from typing import Type, TypeVar, Generic from sqlalchemy.orm import Session +from crudkit.core.base import Version from crudkit.core.spec import CRUDSpec T = TypeVar("T") @@ -48,13 +49,15 @@ class CRUDService(Generic[T]): query = query.offset(offset).limit(limit) return query.all() - def create(self, data: dict) -> T: + def create(self, data: dict, actor=None) -> T: obj = self.model(**data) self.session.add(obj) self.session.commit() + + self._log_version("create", obj, actor) return obj - def update(self, id: int, data: dict) -> T: + def update(self, id: int, data: dict, actor=None) -> T: obj = self.get(id) if not obj: raise ValueError(f"{self.model.__name__} with ID {id} not found.") @@ -64,9 +67,11 @@ class CRUDService(Generic[T]): if k in valid_fields: setattr(obj, k, v) self.session.commit() + + self._log_version("update", obj, actor) return obj - def delete(self, id: int, hard: bool = False): + def delete(self, id: int, hard: bool = False, actor = False): obj = self.session.get(self.model, id) if not obj: return None @@ -77,4 +82,23 @@ class CRUDService(Generic[T]): obj.is_deleted = True self.session.commit() + + self._log_version("delete", obj, actor) return obj + + def _log_version(self, change_type: str, obj: T, actor=None, metadata: dict = {}): + try: + data = obj.as_dict() + except Exception: + data = {"error": "Failed to serialize object."} + + version = Version( + model_name=self.model.__name__, + object_id=obj.id, + change_type=change_type, + data=data, + actor=str(actor) if actor else None, + metadata=metadata + ) + self.session.add(version) + self.session.commit() diff --git a/muck/models/dbref.py b/muck/models/dbref.py index 493fcbb..9bf3c1e 100644 --- a/muck/models/dbref.py +++ b/muck/models/dbref.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy import Column, Integer, String, ForeignKey, Boolean from sqlalchemy.orm import relationship from crudkit.core.base import CRUDMixin, Base @@ -7,6 +7,7 @@ class Dbref(Base, CRUDMixin): type = Column(String, nullable=False) name = Column(String, nullable=False) + is_deleted = Column(Boolean, nullable=False, default=False) owner_id = Column(Integer, ForeignKey("dbref.id")) location_id = Column(Integer, ForeignKey("dbref.id")) @@ -21,3 +22,21 @@ class Dbref(Base, CRUDMixin): def __str__(self): return f"#{self.id} ({self.type}): {self.name}" + + def is_type(self, *types: str) -> bool: + return self.type in types + + @property + def is_room(self): return self.is_type("room") + + @property + def is_thing(self): return self.is_type("thing") + + @property + def is_exit(self): return self.is_type("exit") + + @property + def is_player(self): return self.is_type("player") + + @property + def is_program(self): return self.is_type("programI ho") diff --git a/muck/models/exit.py b/muck/models/exit.py new file mode 100644 index 0000000..3ad018b --- /dev/null +++ b/muck/models/exit.py @@ -0,0 +1,6 @@ +from muck.models.dbref import Dbref + +class Exit(Dbref): + __mapper_args__ = { + "polymorphic_identity": "exit" + } diff --git a/muck/models/player.py b/muck/models/player.py new file mode 100644 index 0000000..be7694b --- /dev/null +++ b/muck/models/player.py @@ -0,0 +1,6 @@ +from muck.models.dbref import Dbref + +class Player(Dbref): + __mapper_args__ = { + "polymorphic_identity": "player" + } diff --git a/muck/models/program.py b/muck/models/program.py new file mode 100644 index 0000000..8854d47 --- /dev/null +++ b/muck/models/program.py @@ -0,0 +1,6 @@ +from muck.models.dbref import Dbref + +class Program(Dbref): + __mapper_args__ = { + "polymorphic_identity": "program" + } diff --git a/muck/models/room.py b/muck/models/room.py new file mode 100644 index 0000000..19bc508 --- /dev/null +++ b/muck/models/room.py @@ -0,0 +1,6 @@ +from muck.models.dbref import Dbref + +class Room(Dbref): + __mapper_args__ = { + "polymorphic_identity": "room" + } diff --git a/muck/models/thing.py b/muck/models/thing.py new file mode 100644 index 0000000..33af801 --- /dev/null +++ b/muck/models/thing.py @@ -0,0 +1,6 @@ +from muck.models.dbref import Dbref + +class Thing(Dbref): + __mapper_args__ = { + "polymorphic_identity": "thing" + }