feat(rulebook): SQLAlchemy models for rulebooks, topics, rules
This commit is contained in:
@@ -37,3 +37,6 @@ from fabledassistant.models.share import NoteShare, ProjectShare # noqa: E402,
|
|||||||
from fabledassistant.models.notification import Notification # noqa: E402, F401
|
from fabledassistant.models.notification import Notification # noqa: E402, F401
|
||||||
from fabledassistant.models.api_key import ApiKey # noqa: E402, F401
|
from fabledassistant.models.api_key import ApiKey # noqa: E402, F401
|
||||||
from fabledassistant.models.user_profile import UserProfile # noqa: E402, F401
|
from fabledassistant.models.user_profile import UserProfile # noqa: E402, F401
|
||||||
|
from fabledassistant.models.rulebook import ( # noqa: E402, F401
|
||||||
|
Rulebook, RulebookTopic, Rule, project_rulebook_subscriptions,
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger, Column, DateTime, ForeignKey, Integer, Table, Text, UniqueConstraint
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from fabledassistant.models import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Rulebook(Base):
|
||||||
|
__tablename__ = "rulebooks"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||||||
|
owner_user_id: Mapped[int] = mapped_column(
|
||||||
|
BigInteger, ForeignKey("users.id", ondelete="CASCADE")
|
||||||
|
)
|
||||||
|
title: Mapped[str] = mapped_column(Text)
|
||||||
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
default=lambda: datetime.now(timezone.utc),
|
||||||
|
onupdate=lambda: datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"owner_user_id": self.owner_user_id,
|
||||||
|
"title": self.title,
|
||||||
|
"description": self.description or "",
|
||||||
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||||
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RulebookTopic(Base):
|
||||||
|
__tablename__ = "rulebook_topics"
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint("rulebook_id", "title", name="uq_topic_per_rulebook"),
|
||||||
|
)
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||||||
|
rulebook_id: Mapped[int] = mapped_column(
|
||||||
|
BigInteger, ForeignKey("rulebooks.id", ondelete="CASCADE")
|
||||||
|
)
|
||||||
|
title: Mapped[str] = mapped_column(Text)
|
||||||
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
order_index: Mapped[int] = mapped_column(Integer, default=0)
|
||||||
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
default=lambda: datetime.now(timezone.utc),
|
||||||
|
onupdate=lambda: datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"rulebook_id": self.rulebook_id,
|
||||||
|
"title": self.title,
|
||||||
|
"description": self.description or "",
|
||||||
|
"order_index": self.order_index,
|
||||||
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||||
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Rule(Base):
|
||||||
|
__tablename__ = "rules"
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint("topic_id", "title", name="uq_rule_per_topic"),
|
||||||
|
)
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||||||
|
topic_id: Mapped[int] = mapped_column(
|
||||||
|
BigInteger, ForeignKey("rulebook_topics.id", ondelete="CASCADE")
|
||||||
|
)
|
||||||
|
title: Mapped[str] = mapped_column(Text)
|
||||||
|
statement: Mapped[str] = mapped_column(Text)
|
||||||
|
why: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
how_to_apply: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
order_index: Mapped[int] = mapped_column(Integer, default=0)
|
||||||
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
default=lambda: datetime.now(timezone.utc),
|
||||||
|
onupdate=lambda: datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"topic_id": self.topic_id,
|
||||||
|
"title": self.title,
|
||||||
|
"statement": self.statement,
|
||||||
|
"why": self.why or "",
|
||||||
|
"how_to_apply": self.how_to_apply or "",
|
||||||
|
"order_index": self.order_index,
|
||||||
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||||
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Pure many-to-many — no model class, just the join table.
|
||||||
|
project_rulebook_subscriptions = Table(
|
||||||
|
"project_rulebook_subscriptions",
|
||||||
|
Base.metadata,
|
||||||
|
Column("project_id", BigInteger, ForeignKey("projects.id", ondelete="CASCADE"), primary_key=True),
|
||||||
|
Column("rulebook_id", BigInteger, ForeignKey("rulebooks.id", ondelete="CASCADE"), primary_key=True),
|
||||||
|
Column("created_at", DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)),
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user