Use capability-based module boundaries

This commit is contained in:
2026-07-10 17:33:43 +02:00
parent d5d5b89603
commit caaace14f3
6 changed files with 15 additions and 11 deletions

View File

@@ -4,7 +4,7 @@ import uuid
from typing import Any from typing import Any
from sqlalchemy import Boolean, ForeignKey, Index, JSON, String, Text, UniqueConstraint from sqlalchemy import Boolean, ForeignKey, Index, JSON, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column
from govoplan_core.db.base import Base, TimestampMixin from govoplan_core.db.base import Base, TimestampMixin
@@ -21,7 +21,7 @@ class MailServerProfile(Base, TimestampMixin):
) )
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
tenant_id: Mapped[str | None] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=True, index=True) tenant_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
scope_type: Mapped[str] = mapped_column(String(20), default="tenant", nullable=False, index=True) scope_type: Mapped[str] = mapped_column(String(20), default="tenant", nullable=False, index=True)
scope_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True) scope_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
name: Mapped[str] = mapped_column(String(255), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False)
@@ -37,8 +37,6 @@ class MailServerProfile(Base, TimestampMixin):
created_by_user_id: Mapped[str | None] = mapped_column(ForeignKey("access_users.id", ondelete="SET NULL"), nullable=True, index=True) created_by_user_id: Mapped[str | None] = mapped_column(ForeignKey("access_users.id", ondelete="SET NULL"), nullable=True, index=True)
updated_by_user_id: Mapped[str | None] = mapped_column(ForeignKey("access_users.id", ondelete="SET NULL"), nullable=True, index=True) updated_by_user_id: Mapped[str | None] = mapped_column(ForeignKey("access_users.id", ondelete="SET NULL"), nullable=True, index=True)
tenant: Mapped["Tenant"] = relationship()
class MailProfilePolicy(Base, TimestampMixin): class MailProfilePolicy(Base, TimestampMixin):
__tablename__ = "mail_profile_policies" __tablename__ = "mail_profile_policies"
@@ -48,8 +46,7 @@ class MailProfilePolicy(Base, TimestampMixin):
) )
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
tenant_id: Mapped[str | None] = mapped_column(ForeignKey("tenancy_tenants.id", ondelete="CASCADE"), nullable=True, index=True) tenant_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
scope_type: Mapped[str] = mapped_column(String(20), nullable=False, index=True) scope_type: Mapped[str] = mapped_column(String(20), nullable=False, index=True)
scope_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True) scope_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
policy: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) policy: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)

View File

@@ -5,7 +5,7 @@ from typing import Any
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
from govoplan_access.auth import ApiPrincipal, require_scope from govoplan_core.auth import ApiPrincipal, require_scope
from govoplan_mail.backend.dev.mock_mailbox import ( from govoplan_mail.backend.dev.mock_mailbox import (
clear_records, clear_records,
get_failures, get_failures,

View File

@@ -281,7 +281,7 @@ def _campaign_policy_context(session: Session, *, tenant_id: str, campaign_id: s
def _legacy_tenant_settings(session: Session, tenant_id: str) -> dict[str, Any] | None: def _legacy_tenant_settings(session: Session, tenant_id: str) -> dict[str, Any] | None:
row = session.execute(text("select settings from tenancy_tenants where id = :tenant_id"), {"tenant_id": tenant_id}).first() row = session.execute(text("select settings from core_scopes where id = :tenant_id"), {"tenant_id": tenant_id}).first()
return _json_object(row[0]) if row else None return _json_object(row[0]) if row else None

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard
from govoplan_core.core.modules import ( from govoplan_core.core.modules import (
DocumentationCondition, DocumentationCondition,
@@ -87,7 +88,7 @@ manifest = ModuleManifest(
id="mail", id="mail",
name="Mail", name="Mail",
version="0.1.6", version="0.1.6",
dependencies=("access",), required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
optional_dependencies=(), optional_dependencies=(),
permissions=PERMISSIONS, permissions=PERMISSIONS,
route_factory=_mail_router, route_factory=_mail_router,

View File

@@ -16,10 +16,16 @@ branch_labels = None
depends_on = None depends_on = None
def _scope_fk_target(inspector) -> str:
tables = set(inspector.get_table_names())
return "core_scopes.id" if "core_scopes" in tables else "tenancy_tenants.id"
def upgrade() -> None: def upgrade() -> None:
inspector = sa.inspect(op.get_bind()) inspector = sa.inspect(op.get_bind())
if "mail_profile_policies" in inspector.get_table_names(): if "mail_profile_policies" in inspector.get_table_names():
return return
scope_fk_target = _scope_fk_target(inspector)
op.create_table( op.create_table(
"mail_profile_policies", "mail_profile_policies",
sa.Column("id", sa.String(length=36), nullable=False), sa.Column("id", sa.String(length=36), nullable=False),
@@ -29,7 +35,7 @@ def upgrade() -> None:
sa.Column("policy", sa.JSON(), nullable=False), sa.Column("policy", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenancy_tenants.id"], name=op.f("fk_mail_profile_policies_tenant_id_tenants"), ondelete="CASCADE"), sa.ForeignKeyConstraint(["tenant_id"], [scope_fk_target], name=op.f("fk_mail_profile_policies_tenant_id_scopes"), ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id", name=op.f("pk_mail_profile_policies")), sa.PrimaryKeyConstraint("id", name=op.f("pk_mail_profile_policies")),
sa.UniqueConstraint("tenant_id", "scope_type", "scope_id", name="uq_mail_profile_policies_scope"), sa.UniqueConstraint("tenant_id", "scope_type", "scope_id", name="uq_mail_profile_policies_scope"),
) )

View File

@@ -23,7 +23,7 @@ from govoplan_mail.backend.schemas import (
MailServerProfileUpdateRequest, MailServerProfileUpdateRequest,
MailSmtpTestRequest, MailSmtpTestRequest,
) )
from govoplan_access.auth import ApiPrincipal, get_api_principal, has_scope from govoplan_core.auth import ApiPrincipal, get_api_principal, has_scope
from govoplan_core.api.v1.schemas import DeltaDeletedItem from govoplan_core.api.v1.schemas import DeltaDeletedItem
from govoplan_core.core.change_sequence import ( from govoplan_core.core.change_sequence import (
ChangeSequenceEntry, ChangeSequenceEntry,