first implementation

This commit is contained in:
counterweight 2025-12-20 11:12:11 +01:00
parent 79458bcba4
commit 870804e7b9
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
24 changed files with 5485 additions and 184 deletions

View file

@ -23,6 +23,17 @@ class Permission(str, PyEnum):
# Audit permissions
VIEW_AUDIT = "view_audit"
# Invite permissions
MANAGE_INVITES = "manage_invites"
VIEW_OWN_INVITES = "view_own_invites"
class InviteStatus(str, PyEnum):
"""Status of an invite."""
READY = "ready"
SPENT = "spent"
REVOKED = "revoked"
# Role name constants
@ -32,17 +43,19 @@ ROLE_REGULAR = "regular"
# Role definitions with their permissions
ROLE_DEFINITIONS: dict[str, RoleConfig] = {
ROLE_ADMIN: {
"description": "Administrator with audit access",
"description": "Administrator with audit and invite management access",
"permissions": [
Permission.VIEW_AUDIT,
Permission.MANAGE_INVITES,
],
},
ROLE_REGULAR: {
"description": "Regular user with counter and sum access",
"description": "Regular user with counter, sum, and invite access",
"permissions": [
Permission.VIEW_COUNTER,
Permission.INCREMENT_COUNTER,
Permission.USE_SUM,
Permission.VIEW_OWN_INVITES,
],
},
}
@ -107,6 +120,16 @@ class User(Base):
signal: Mapped[str | None] = mapped_column(String(64), nullable=True)
nostr_npub: Mapped[str | None] = mapped_column(String(63), nullable=True)
# Godfather (who invited this user) - null for seeded/admin users
godfather_id: Mapped[int | None] = mapped_column(
Integer, ForeignKey("users.id"), nullable=True
)
godfather: Mapped["User | None"] = relationship(
"User",
remote_side="User.id",
foreign_keys=[godfather_id],
)
# Relationship to roles
roles: Mapped[list[Role]] = relationship(
"Role",
@ -165,3 +188,40 @@ class CounterRecord(Base):
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(UTC)
)
class Invite(Base):
__tablename__ = "invites"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
identifier: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True)
status: Mapped[InviteStatus] = mapped_column(
Enum(InviteStatus), nullable=False, default=InviteStatus.READY
)
# Godfather - the user who owns this invite
godfather_id: Mapped[int] = mapped_column(
Integer, ForeignKey("users.id"), nullable=False, index=True
)
godfather: Mapped[User] = relationship(
"User",
foreign_keys=[godfather_id],
lazy="selectin",
)
# User who used this invite (null until spent)
used_by_id: Mapped[int | None] = mapped_column(
Integer, ForeignKey("users.id"), nullable=True
)
used_by: Mapped[User | None] = relationship(
"User",
foreign_keys=[used_by_id],
lazy="selectin",
)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(UTC)
)
spent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)