from sqlalchemy import ForeignKey, Integer, String, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, mapped_column, relationship from database import Base from .associations import role_permissions, user_roles from .enums import Permission class Role(Base): __tablename__ = "roles" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False) description: Mapped[str] = mapped_column(String(255), nullable=True) # Relationship to users users: Mapped[list["User"]] = relationship( "User", secondary=user_roles, back_populates="roles", ) async def get_permissions(self, db: AsyncSession) -> set[Permission]: """Get all permissions for this role.""" query = select(role_permissions.c.permission).where( role_permissions.c.role_id == self.id ) result = await db.execute(query) return {row[0] for row in result.fetchall()} async def set_permissions( self, db: AsyncSession, permissions: list[Permission] ) -> None: """Set all permissions for this role (replaces existing).""" delete_query = role_permissions.delete().where( role_permissions.c.role_id == self.id ) await db.execute(delete_query) for perm in permissions: insert_query = role_permissions.insert().values( role_id=self.id, permission=perm ) await db.execute(insert_query) class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) email: Mapped[str] = mapped_column( String(255), unique=True, nullable=False, index=True ) hashed_password: Mapped[str] = mapped_column(String(255), nullable=False) # Contact details (all optional) contact_email: Mapped[str | None] = mapped_column(String(255), nullable=True) telegram: Mapped[str | None] = mapped_column(String(64), nullable=True) 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", secondary=user_roles, back_populates="users", lazy="selectin", ) async def get_permissions(self, db: AsyncSession) -> set[Permission]: """Get all permissions from all roles in a single query.""" result = await db.execute( select(role_permissions.c.permission) .join(user_roles, role_permissions.c.role_id == user_roles.c.role_id) .where(user_roles.c.user_id == self.id) ) return {row[0] for row in result.fetchall()} async def has_permission(self, db: AsyncSession, permission: Permission) -> bool: """Check if user has a specific permission through any of their roles.""" permissions = await self.get_permissions(db) return permission in permissions @property def role_names(self) -> list[str]: """Get list of role names for API responses.""" return [role.name for role in self.roles]