Create access module package
This commit is contained in:
236
src/govoplan_access/backend/db/models.py
Normal file
236
src/govoplan_access/backend/db/models.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user