from __future__ import annotations from datetime import datetime from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, Table, Text from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.session import Base role_permissions = Table( "role_permissions", Base.metadata, Column("role_id", ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True), Column("permission_id", ForeignKey("permissions.id", ondelete="CASCADE"), primary_key=True), ) class Role(Base): __tablename__ = "roles" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(64), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) permissions: Mapped[list["Permission"]] = relationship( secondary=role_permissions, back_populates="roles", lazy="selectin", ) users: Mapped[list["User"]] = relationship(back_populates="role") class Permission(Base): __tablename__ = "permissions" id: Mapped[int] = mapped_column(primary_key=True) key: Mapped[str] = mapped_column(String(128), unique=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) roles: Mapped[list["Role"]] = relationship( secondary=role_permissions, back_populates="permissions", ) class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String(255), unique=True, index=True) name: Mapped[str] = mapped_column(String(255)) role_id: Mapped[int | None] = mapped_column(ForeignKey("roles.id"), nullable=True, index=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) # Per-user password hash (PBKDF2-SHA256). Null while a user has never set # a personal password — they can still sign in with the shared internal # password until they choose one in settings. password_hash: Mapped[str | None] = mapped_column(String(255), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) role: Mapped["Role | None"] = relationship(back_populates="users", lazy="selectin")