Initial Claude implementation

This commit is contained in:
Ian Roddis committed 2026-06-22 14:26:44 -03:00
commit e2d3ab9c2a
63 files changed
+3930

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
"""Import all models so they register with ``Base.metadata``."""
from app.models.associations import role_permissions, user_roles
from app.models.permission import Permission
from app.models.refresh_token import RefreshToken
from app.models.role import Role
from app.models.user import User
__all__ = [
"Permission",
"RefreshToken",
"Role",
"User",
"role_permissions",
"user_roles",
]
+18
View File
@@ -0,0 +1,18 @@
"""Many-to-many association tables for RBAC."""
from sqlalchemy import Column, ForeignKey, Table
from app.core.database import Base
user_roles = Table(
"user_roles",
Base.metadata,
Column("user_id", ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
Column("role_id", ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True),
)
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),
)
+19
View File
@@ -0,0 +1,19 @@
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
from app.models.associations import role_permissions
class Permission(Base):
__tablename__ = "permissions"
id: Mapped[int] = mapped_column(primary_key=True)
# Convention: "<resource>:<action>", e.g. "users:read".
name: Mapped[str] = mapped_column(String(128), unique=True, index=True)
description: Mapped[str | None] = mapped_column(String(255), default=None)
roles: Mapped[list["Role"]] = relationship( # noqa: F821
secondary=role_permissions,
back_populates="permissions",
)
+29
View File
@@ -0,0 +1,29 @@
from datetime import datetime
from sqlalchemy import Boolean, DateTime, ForeignKey, String, func
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
class RefreshToken(Base):
"""Server-side record of an issued refresh token, enabling revocation.
The raw token is never stored — only its jti (for lookup) and a SHA-256
hash (for verification). Rotation marks the old row revoked and inserts a
new one; logout revokes the row(s) for a user.
"""
__tablename__ = "refresh_tokens"
id: Mapped[int] = mapped_column(primary_key=True)
jti: Mapped[str] = mapped_column(String(64), unique=True, index=True)
token_hash: Mapped[str] = mapped_column(String(64))
user_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), index=True
)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
revoked: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
+23
View File
@@ -0,0 +1,23 @@
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
from app.models.associations import role_permissions, user_roles
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(String(255), default=None)
users: Mapped[list["User"]] = relationship( # noqa: F821
secondary=user_roles,
back_populates="roles",
)
permissions: Mapped[list["Permission"]] = relationship( # noqa: F821
secondary=role_permissions,
back_populates="roles",
lazy="selectin",
)
+35
View File
@@ -0,0 +1,35 @@
from datetime import datetime
from sqlalchemy import Boolean, DateTime, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
from app.models.associations import user_roles
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
hashed_password: Mapped[str] = mapped_column(String(255))
full_name: Mapped[str | None] = mapped_column(String(255), default=None)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
roles: Mapped[list["Role"]] = relationship( # noqa: F821
secondary=user_roles,
back_populates="users",
lazy="selectin",
)
@property
def permissions(self) -> set[str]:
"""Flattened set of permission names granted via this user's roles."""
return {perm.name for role in self.roles for perm in role.permissions}
def has_permission(self, name: str) -> bool:
return self.is_superuser or name in self.permissions