|
|
|
@@ -0,0 +1,236 @@
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import uuid
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint, text
|
|
|
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
|
|
|
|
|
|
from govoplan_access.backend.db.base import AccessBase, TimestampMixin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def new_uuid() -> str:
|
|
|
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Account(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_accounts"
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
email: Mapped[str] = mapped_column(String(320), nullable=False)
|
|
|
|
|
normalized_email: Mapped[str] = mapped_column(String(320), nullable=False, unique=True, index=True)
|
|
|
|
|
display_name: Mapped[str | None] = mapped_column(String(255))
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
auth_provider: Mapped[str] = mapped_column(String(50), default="local", nullable=False)
|
|
|
|
|
password_hash: Mapped[str | None] = mapped_column(String(500))
|
|
|
|
|
password_reset_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
|
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
|
|
|
|
|
|
memberships: Mapped[list[Membership]] = relationship(back_populates="account", cascade="all, delete-orphan")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Tenant(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_tenants"
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, index=True)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
|
|
|
default_locale: Mapped[str] = mapped_column(String(20), default="en", nullable=False)
|
|
|
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
|
|
|
|
|
memberships: Mapped[list[Membership]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Membership(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_memberships"
|
|
|
|
|
__table_args__ = (UniqueConstraint("tenant_id", "account_id", name="uq_access_memberships_tenant_account"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
email_snapshot: Mapped[str | None] = mapped_column(String(320), index=True)
|
|
|
|
|
display_name: Mapped[str | None] = mapped_column(String(255))
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
|
|
|
|
account: Mapped[Account] = relationship(back_populates="memberships")
|
|
|
|
|
tenant: Mapped[Tenant] = relationship(back_populates="memberships")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Group(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_groups"
|
|
|
|
|
__table_args__ = (UniqueConstraint("tenant_id", "slug", name="uq_access_groups_tenant_slug"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
template_id: Mapped[str | None] = mapped_column(String(100), index=True)
|
|
|
|
|
is_protected: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GroupMembership(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_group_memberships"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
UniqueConstraint("tenant_id", "membership_id", "group_id", name="uq_access_group_memberships_member_group"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
membership_id: Mapped[str] = mapped_column(ForeignKey("access_memberships.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
group_id: Mapped[str] = mapped_column(ForeignKey("access_groups.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Role(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_roles"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
UniqueConstraint("tenant_id", "slug", name="uq_access_roles_tenant_slug"),
|
|
|
|
|
Index(
|
|
|
|
|
"uq_access_roles_system_slug",
|
|
|
|
|
"slug",
|
|
|
|
|
unique=True,
|
|
|
|
|
sqlite_where=text("tenant_id IS NULL"),
|
|
|
|
|
postgresql_where=text("tenant_id IS NULL"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str | None] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
|
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
|
|
|
permissions: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
|
|
|
level: Mapped[str] = mapped_column(String(20), default="tenant", nullable=False, index=True)
|
|
|
|
|
is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
|
is_assignable: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
template_id: Mapped[str | None] = mapped_column(String(100), index=True)
|
|
|
|
|
is_protected: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RoleBinding(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_role_bindings"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_access_role_bindings_subject", "subject_type", "subject_id"),
|
|
|
|
|
Index("ix_access_role_bindings_tenant_subject", "tenant_id", "subject_type", "subject_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str | None] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
|
|
|
role_id: Mapped[str] = mapped_column(ForeignKey("access_roles.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
subject_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
|
|
|
subject_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
|
|
|
created_by_account_id: Mapped[str | None] = mapped_column(String(36), index=True)
|
|
|
|
|
created_by_membership_id: Mapped[str | None] = mapped_column(String(36), index=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PermissionCatalogEntry(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_permission_catalog"
|
|
|
|
|
|
|
|
|
|
scope: Mapped[str] = mapped_column(String(255), primary_key=True)
|
|
|
|
|
module_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
|
|
|
|
resource: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
|
|
|
action: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
|
|
|
label: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
description: Mapped[str] = mapped_column(Text, default="", nullable=False)
|
|
|
|
|
category: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
|
|
|
level: Mapped[str] = mapped_column(String(20), nullable=False, index=True)
|
|
|
|
|
is_deprecated: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RoleTemplateModel(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_role_templates"
|
|
|
|
|
__table_args__ = (UniqueConstraint("module_id", "level", "slug", name="uq_access_role_templates_module_level_slug"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
module_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
|
|
|
|
slug: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
|
|
|
permissions: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
|
|
|
level: Mapped[str] = mapped_column(String(20), default="tenant", nullable=False)
|
|
|
|
|
managed: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
protected: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ModuleInstallation(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_module_installations"
|
|
|
|
|
|
|
|
|
|
module_id: Mapped[str] = mapped_column(String(100), primary_key=True)
|
|
|
|
|
version: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
|
|
|
enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthSession(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_auth_sessions"
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str | None] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
|
|
|
membership_id: Mapped[str | None] = mapped_column(ForeignKey("access_memberships.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
|
|
|
account_id: Mapped[str] = mapped_column(ForeignKey("access_accounts.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
token_hash: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True)
|
|
|
|
|
csrf_token_hash: Mapped[str | None] = mapped_column(String(255))
|
|
|
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
|
|
|
|
|
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
|
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
|
|
|
|
|
user_agent: Mapped[str | None] = mapped_column(String(500))
|
|
|
|
|
ip_address: Mapped[str | None] = mapped_column(String(100))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ApiKey(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_api_keys"
|
|
|
|
|
__table_args__ = (Index("ix_access_api_keys_owner", "owner_subject_type", "owner_subject_id"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
owner_subject_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
|
|
|
owner_subject_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
prefix: Mapped[str] = mapped_column(String(20), nullable=False, index=True)
|
|
|
|
|
key_hash: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
|
|
|
|
|
scopes: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
|
|
|
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
|
last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
|
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TenantDatastore(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_tenant_datastores"
|
|
|
|
|
__table_args__ = (UniqueConstraint("tenant_id", "module_id", name="uq_access_tenant_datastores_tenant_module"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
|
|
|
|
|
tenant_id: Mapped[str] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
module_id: Mapped[str] = mapped_column(String(100), default="*", nullable=False)
|
|
|
|
|
mode: Mapped[str] = mapped_column(String(20), default="shared", nullable=False)
|
|
|
|
|
database_url_secret_ref: Mapped[str | None] = mapped_column(String(255))
|
|
|
|
|
schema_name: Mapped[str | None] = mapped_column(String(100))
|
|
|
|
|
storage_bucket: Mapped[str | None] = mapped_column(String(255))
|
|
|
|
|
region: Mapped[str | None] = mapped_column(String(100))
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SystemSettings(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_system_settings"
|
|
|
|
|
|
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default="global")
|
|
|
|
|
default_locale: Mapped[str] = mapped_column(String(20), default="en", nullable=False)
|
|
|
|
|
allow_tenant_custom_groups: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
allow_tenant_custom_roles: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
allow_tenant_api_keys: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
|
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TenantSettings(AccessBase, TimestampMixin):
|
|
|
|
|
__tablename__ = "access_tenant_settings"
|
|
|
|
|
|
|
|
|
|
tenant_id: Mapped[str] = mapped_column(ForeignKey("access_tenants.id", ondelete="CASCADE"), primary_key=True)
|
|
|
|
|
allow_custom_groups: Mapped[bool | None] = mapped_column(Boolean)
|
|
|
|
|
allow_custom_roles: Mapped[bool | None] = mapped_column(Boolean)
|
|
|
|
|
allow_api_keys: Mapped[bool | None] = mapped_column(Boolean)
|
|
|
|
|
settings: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|