Phase 2: Visibility Filter & Owner ID
Check Cross-Plugin Imports / check (push) Has been cancelled

- Add OwnedMixin to 15 models (contact_folder, user_preference, workspace,
  mcp_server_config, agent_definition, automation_definition, report_template,
  report_instance, entity_link, comm_conversation, proactive_suggestion,
  ai_agent, ai_chat_session, tag, share_link)
- Migration 0102: Add owner_id column to 15 tables with backfill from user_id
- Fix EntityPermission Registry: remove notification, add entity_attachment,
  entity_history, subtask, calendar, folder; fix wrong class names
  (DmsFile→File, CalendarEvent→CalendarEntry, Mailbox→MailAccount)
- Add apply_visibility_filter to list endpoints in tags, tasks, mcp_client,
  automation, report_generator, ai_assistant routes
- Add owner_id to create handlers for all new OwnedMixin models
- Patch tasks/services.py and automation/services.py list methods with
  user_id and is_system_admin parameters
This commit is contained in:
Agent Zero
2026-08-04 00:03:29 +02:00
parent 93a330ae40
commit e17b9c9e56
29 changed files with 391 additions and 36 deletions
+2 -1
View File
@@ -14,9 +14,10 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class ContactFolder(Base, TenantMixin):
class ContactFolder(Base, TenantMixin, OwnedMixin):
"""Hierarchical folder for organizing contacts.
Folders are tenant-scoped and user-owned. A folder with parent_id=NULL
+2 -1
View File
@@ -11,9 +11,10 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class UserPreference(Base, TenantMixin):
class UserPreference(Base, TenantMixin, OwnedMixin):
"""Per-user preference entry — stores a single UI preference as JSONB value.
Keys are arbitrary strings (e.g. 'sidebar_collapsed', 'theme', 'active_tab').
+2 -1
View File
@@ -30,9 +30,10 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class Workspace(Base, TenantMixin):
class Workspace(Base, TenantMixin, OwnedMixin):
"""A workspace is a UI/navigation context for a user.
It defines which modules are visible, which dashboard widgets appear,
+3 -2
View File
@@ -19,6 +19,7 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
# --- Providers ---
@@ -101,7 +102,7 @@ class AIPreset(Base, TenantMixin):
# --- Agents ---
class AIAgent(Base, TenantMixin):
class AIAgent(Base, TenantMixin, OwnedMixin):
"""AI agent with system prompt and assigned tools."""
__tablename__ = "ai_agents"
@@ -128,7 +129,7 @@ class AIAgent(Base, TenantMixin):
# --- Chat Sessions ---
class AIChatSession(Base, TenantMixin):
class AIChatSession(Base, TenantMixin, OwnedMixin):
"""Chat session for a user with a specific agent."""
__tablename__ = "ai_chat_sessions"
+13 -3
View File
@@ -330,9 +330,13 @@ async def list_agents(
db: AsyncSession = Depends(get_db),
):
tenant_id = uuid.UUID(current_user["tenant_id"])
result = await db.execute(
select(AIAgent).where(AIAgent.tenant_id == tenant_id)
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
query = select(AIAgent).where(AIAgent.tenant_id == tenant_id)
query = await apply_visibility_filter(
db, query, "ai_agent", AIAgent, user_id, tenant_id, is_system_admin
)
result = await db.execute(query)
agents = list(result.scalars().all())
return [agent_to_response(a) for a in agents]
@@ -344,6 +348,7 @@ async def create_agent(
db: AsyncSession = Depends(get_db),
):
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
await set_tenant_context(db, tenant_id)
agent = AIAgent(
@@ -355,6 +360,7 @@ async def create_agent(
is_active=data.is_active,
config=data.config,
tenant_id=tenant_id,
owner_id=user_id,
)
db.add(agent)
await db.commit()
@@ -422,10 +428,13 @@ async def list_sessions(
):
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
stmt = (
select(AIChatSession)
.where(AIChatSession.tenant_id == tenant_id)
.where(AIChatSession.user_id == user_id)
)
stmt = await apply_visibility_filter(
db, stmt, "ai_chat_session", AIChatSession, user_id, tenant_id, is_system_admin
)
if is_sidebar is not None:
stmt = stmt.where(AIChatSession.is_sidebar == is_sidebar)
@@ -470,6 +479,7 @@ async def create_session(
is_sidebar=data.is_sidebar,
folder_id=folder_id,
tenant_id=tenant_id,
owner_id=user_id,
)
db.add(session)
await db.commit()
+2 -1
View File
@@ -20,9 +20,10 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class ProactiveSuggestion(Base, TenantMixin):
class ProactiveSuggestion(Base, TenantMixin, OwnedMixin):
"""A proactive AI suggestion generated from user context."""
__tablename__ = "ai_proactive_suggestions"
@@ -16,6 +16,7 @@ from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db, set_tenant_context
from app.core.visibility import apply_visibility_filter
from app.core.event_bus import get_event_bus
from app.deps import get_current_user, require_permission
from app.plugins.builtins.ai_proactive.models import (
@@ -13,6 +13,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db, set_tenant_context
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.automation.models import (
AgentDefinition,
@@ -112,8 +113,11 @@ async def list_agents(
):
"""List agent definitions with optional filters."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
items, total = await AgentService.list(
db, tenant_id, is_active=is_active, mode=mode, limit=limit, offset=offset
db, tenant_id, is_active=is_active, mode=mode, limit=limit, offset=offset,
user_id=user_id, is_system_admin=is_system_admin,
)
return AgentDefinitionListResponse(
items=[_agent_to_response(a) for a in items],
+3 -2
View File
@@ -21,9 +21,10 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class AgentDefinition(Base, TenantMixin):
class AgentDefinition(Base, TenantMixin, OwnedMixin):
"""An AI agent definition — configures an LLM-powered agent with tools and behavior."""
__tablename__ = "automation_agent_definitions"
@@ -89,7 +90,7 @@ class AgentVersion(Base, TenantMixin):
)
class AutomationDefinition(Base, TenantMixin):
class AutomationDefinition(Base, TenantMixin, OwnedMixin):
"""An automation workflow definition — event/schedule/manual triggered with conditions and actions."""
__tablename__ = "automation_definitions"
@@ -13,6 +13,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db, set_tenant_context
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.automation.models import (
AutomationDefinition,
@@ -112,9 +113,12 @@ async def list_automations(
):
"""List automation definitions with optional filters."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
items, total = await AutomationService.list(
db, tenant_id, trigger_type=trigger_type, is_active=is_active,
limit=limit, offset=offset,
user_id=user_id, is_system_admin=is_system_admin,
)
return AutomationDefinitionListResponse(
items=[_automation_to_response(a) for a in items],
@@ -11,6 +11,7 @@ from datetime import UTC, datetime
from typing import Any
from sqlalchemy import func, select, text, update
from app.core.visibility import apply_visibility_filter
from sqlalchemy.ext.asyncio import AsyncSession
from app.plugins.builtins.automation.models import (
@@ -40,9 +41,15 @@ class AgentService:
mode: str | None = None,
limit: int = 50,
offset: int = 0,
user_id: uuid.UUID | None = None,
is_system_admin: bool = False,
) -> tuple[list[AgentDefinition], int]:
"""List agent definitions with optional filters."""
query = select(AgentDefinition).where(AgentDefinition.tenant_id == tenant_id)
if user_id and not is_system_admin:
query = await apply_visibility_filter(
db, query, "agent_definition", AgentDefinition, user_id, tenant_id, is_system_admin
)
count_query = select(func.count()).select_from(AgentDefinition).where(
AgentDefinition.tenant_id == tenant_id
)
@@ -108,6 +115,7 @@ class AgentService:
max_duration_seconds=data.get("max_duration_seconds", 300),
budget_limit_usd=data.get("budget_limit_usd", 1.0),
created_by=user_id,
owner_id=user_id,
)
db.add(agent)
await db.flush()
@@ -291,11 +299,17 @@ class AutomationService:
is_active: bool | None = None,
limit: int = 50,
offset: int = 0,
user_id: uuid.UUID | None = None,
is_system_admin: bool = False,
) -> tuple[list[AutomationDefinition], int]:
"""List automation definitions with optional filters."""
query = select(AutomationDefinition).where(
AutomationDefinition.tenant_id == tenant_id
)
if user_id and not is_system_admin:
query = await apply_visibility_filter(
db, query, "automation_definition", AutomationDefinition, user_id, tenant_id, is_system_admin
)
count_query = (
select(func.count())
.select_from(AutomationDefinition)
@@ -366,6 +380,7 @@ class AutomationService:
is_active=data.get("is_active", True),
dry_run=data.get("dry_run", False),
created_by=user_id,
owner_id=user_id,
)
db.add(automation)
await db.flush()
+2 -1
View File
@@ -9,9 +9,10 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class EntityLink(Base, TenantMixin):
class EntityLink(Base, TenantMixin, OwnedMixin):
"""N:M link between files/folders and companies/contacts."""
__tablename__ = "entity_links"
@@ -9,6 +9,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.entity_links.models import EntityLink
from app.plugins.builtins.entity_links.schemas import EntityLinkRequest
@@ -92,6 +93,7 @@ async def unlink_file_from_entity(
):
"""Remove a link between a file and an entity."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
fid = _parse_uuid(file_id, "file_id")
entity_id = _parse_uuid(body.entity_id, "entity_id")
@@ -119,6 +121,7 @@ async def list_file_links(
):
"""List all entities linked to a file."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
fid = _parse_uuid(file_id, "file_id")
result = await db.execute(
+2 -1
View File
@@ -21,9 +21,10 @@ from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class CommConversation(Base, TenantMixin):
class CommConversation(Base, TenantMixin, OwnedMixin):
"""Conversation / Room — tenant-scoped, supports pinning, locking, archiving."""
__tablename__ = "comm_conversations"
@@ -11,6 +11,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File,
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.kommunikation.rbac import CommRBAC
from app.plugins.builtins.kommunikation.schemas import (
@@ -258,6 +258,7 @@ async def create_conversation(
conv = CommConversation(
tenant_id=tenant_id,
title=title,
owner_id=user_id,
is_direct=is_direct,
created_by=user_id,
created_by_type="user",
@@ -1068,6 +1069,7 @@ async def create_plugin_room(
conv = CommConversation(
tenant_id=tenant_id,
title=title,
owner_id=user_id,
is_locked=True,
locked_by=plugin_name,
is_direct=False,
+2 -1
View File
@@ -10,9 +10,10 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class McpServerConfig(Base, TenantMixin):
class McpServerConfig(Base, TenantMixin, OwnedMixin):
"""Configuration for an external MCP server — tenant-scoped."""
__tablename__ = "mcp_server_configs"
+9 -1
View File
@@ -12,6 +12,7 @@ from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.mcp_client.client import McpClient
from app.plugins.builtins.mcp_client.models import McpServerConfig as McpServerConfigModel
@@ -50,7 +51,13 @@ async def list_mcp_servers(
current_user: dict[str, Any] = Depends(require_permission("mcp-client:read")),
) -> list[McpServerConfigResponse]:
"""List all configured MCP servers for the current tenant."""
stmt = select(McpServerConfigModel).where(McpServerConfigModel.tenant_id == uuid.UUID(current_user["tenant_id"]))
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
stmt = select(McpServerConfigModel).where(McpServerConfigModel.tenant_id == tenant_id)
stmt = await apply_visibility_filter(
db, stmt, "mcp_server_config", McpServerConfigModel, user_id, tenant_id, is_system_admin
)
result = await db.execute(stmt)
configs = result.scalars().all()
return [_config_to_response(c) for c in configs]
@@ -71,6 +78,7 @@ async def create_mcp_server(
enabled=body.enabled,
description=body.description,
created_by=uuid.UUID(current_user["user_id"]),
owner_id=uuid.UUID(current_user["user_id"]),
)
db.add(cfg)
await db.commit()
+2 -1
View File
@@ -10,6 +10,7 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class Permission(Base, TenantMixin):
@@ -37,7 +38,7 @@ class Permission(Base, TenantMixin):
access_level: Mapped[str] = mapped_column(String(10), nullable=False, default="read")
class ShareLink(Base, TenantMixin):
class ShareLink(Base, TenantMixin, OwnedMixin):
"""Public share link for a file — optional password and expiry."""
__tablename__ = "share_links"
@@ -12,6 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.core.auth import hash_password, verify_password
from app.core.db import get_db
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.permissions.models import Permission, ShareLink
from app.plugins.builtins.permissions.schemas import (
@@ -48,6 +49,7 @@ async def list_permissions(
):
"""List all permissions for a file."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
fid = _parse_uuid(file_id, "file_id")
result = await db.execute(
@@ -78,6 +80,7 @@ async def grant_permission(
):
"""Grant a permission on a file to a user."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
fid = _parse_uuid(file_id, "file_id")
user_id = _parse_uuid(body.user_id, "user_id")
group_id = _parse_uuid(body.group_id, "group_id") if body.group_id else None
@@ -123,6 +126,7 @@ async def revoke_permission(
):
"""Revoke all permissions for a user on a file."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
fid = _parse_uuid(file_id, "file_id")
uid = _parse_uuid(user_id, "user_id")
@@ -9,9 +9,10 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class ReportTemplate(Base, TenantMixin):
class ReportTemplate(Base, TenantMixin, OwnedMixin):
"""Report template entity — Jinja2 or SQL template, tenant-scoped, soft-deletable."""
__tablename__ = "report_templates"
@@ -35,7 +36,7 @@ class ReportTemplate(Base, TenantMixin):
created_by: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
class ReportInstance(Base, TenantMixin):
class ReportInstance(Base, TenantMixin, OwnedMixin):
"""Report instance entity — a generated report, tenant-scoped."""
__tablename__ = "report_instances"
@@ -16,6 +16,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.contact import Contact
from app.core.visibility import apply_visibility_filter
from app.models.audit import AuditLog
from app.core.db import get_db, set_tenant_context
@@ -292,12 +293,16 @@ async def list_templates(
):
"""List all report templates for the current tenant."""
tenant_id = uuid.UUID(current_user["tenant_id"])
result = await db.execute(
select(ReportTemplate).where(
ReportTemplate.tenant_id == tenant_id,
ReportTemplate.deleted_at.is_(None),
)
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
query = select(ReportTemplate).where(
ReportTemplate.tenant_id == tenant_id,
ReportTemplate.deleted_at.is_(None),
)
query = await apply_visibility_filter(
db, query, "report_template", ReportTemplate, user_id, tenant_id, is_system_admin
)
result = await db.execute(query)
templates = result.scalars().all()
return [_template_to_response(t).model_dump() for t in templates]
@@ -319,6 +324,7 @@ async def create_template(
content=body.content,
output_format=body.output_format,
created_by=user_id,
owner_id=user_id,
)
db.add(template)
await db.flush()
+2 -1
View File
@@ -9,9 +9,10 @@ from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
from app.models.owned_mixin import OwnedMixin
class Tag(Base, TenantMixin):
class Tag(Base, TenantMixin, OwnedMixin):
"""Tag entity — globally managed, tenant-scoped."""
__tablename__ = "tags"
+10 -2
View File
@@ -9,6 +9,7 @@ from sqlalchemy import delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.tags.models import Tag, TagAssignment
from app.plugins.builtins.tags.schemas import (
@@ -40,6 +41,8 @@ async def list_tags(
):
"""List all tags with entity counts."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
# Subquery for entity counts per tag
count_sq = (
@@ -52,12 +55,16 @@ async def list_tags(
.subquery()
)
result = await db.execute(
query = (
select(Tag, func.coalesce(count_sq.c.entity_count, 0))
.outerjoin(count_sq, Tag.id == count_sq.c.tag_id)
.where(Tag.tenant_id == tenant_id)
.order_by(Tag.name)
)
query = await apply_visibility_filter(
db, query, "tag", Tag, user_id, tenant_id, is_system_admin
)
result = await db.execute(query)
rows = result.all()
return [
@@ -79,6 +86,7 @@ async def create_tag(
):
"""Create a new tag."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
# Check name uniqueness within tenant
existing = await db.execute(
@@ -87,7 +95,7 @@ async def create_tag(
if existing.scalar_one_or_none() is not None:
raise HTTPException(409, detail={"detail": "Tag name already exists", "code": "duplicate"})
tag = Tag(tenant_id=tenant_id, name=body.name, color=body.color)
tag = Tag(tenant_id=tenant_id, name=body.name, color=body.color, owner_id=user_id)
db.add(tag)
await db.flush()
return {
+4
View File
@@ -8,6 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.core.visibility import apply_visibility_filter
from app.deps import get_current_user, require_permission
from app.plugins.builtins.tasks import services
from app.plugins.builtins.tasks.schemas import (
@@ -43,12 +44,15 @@ async def list_tasks(
):
"""List tasks with filtering and pagination."""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
return await services.list_tasks(
db, tenant_id,
page=page, page_size=page_size,
status=status, priority=priority,
assigned_to=assigned_to, contact_id=contact_id,
search=search,
user_id=user_id, is_system_admin=is_system_admin,
)
+9
View File
@@ -9,6 +9,7 @@ from typing import Any
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.visibility import apply_visibility_filter
from app.plugins.builtins.tasks.models import Task
@@ -40,10 +41,17 @@ async def list_tasks(
assigned_to: str | None = None,
contact_id: str | None = None,
search: str | None = None,
user_id: uuid.UUID | None = None,
is_system_admin: bool = False,
) -> dict[str, Any]:
"""List tasks with filtering and pagination."""
query = select(Task).where(Task.tenant_id == tenant_id, Task.deleted_at.is_(None))
if user_id and not is_system_admin:
query = await apply_visibility_filter(
db, query, "task", Task, user_id, tenant_id, is_system_admin
)
if status:
query = query.where(Task.status == status)
if priority:
@@ -103,6 +111,7 @@ async def create_task(
assigned_to=uuid.UUID(data["assigned_to"]) if data.get("assigned_to") else None,
contact_id=uuid.UUID(data["contact_id"]) if data.get("contact_id") else None,
created_by=user_id,
owner_id=user_id,
)
db.add(task)
await db.flush()
+34 -9
View File
@@ -36,7 +36,6 @@ from app.models.sequence import Sequence
from app.models.saved_filter import SavedFilter
from app.models.saved_view import SavedView
from app.models.webhook import Webhook
from app.models.notification import Notification
from app.models.custom_field_definition import CustomFieldDefinition
from app.models.contact_folder import ContactFolder
@@ -56,20 +55,46 @@ ENTITY_MODELS: dict[str, type] = {
"saved_filter": SavedFilter,
"saved_view": SavedView,
"webhook": Webhook,
"notification": Notification,
"custom_field_definition": CustomFieldDefinition,
"contact_folder": ContactFolder,
}
# Try to add plugin models if available
# Core models with OwnedMixin (Phase 2 additions)
try:
from app.plugins.builtins.dms.models import DmsFile
ENTITY_MODELS["dms_file"] = DmsFile
from app.models.entity_attachment import EntityAttachment
ENTITY_MODELS["entity_attachment"] = EntityAttachment
except ImportError:
pass
try:
from app.plugins.builtins.calendar.models import CalendarEvent
ENTITY_MODELS["calendar_event"] = CalendarEvent
from app.models.entity_history import EntityHistory
ENTITY_MODELS["entity_history"] = EntityHistory
except ImportError:
pass
# Plugin models if available
try:
from app.plugins.builtins.dms.models import File as DmsFile
ENTITY_MODELS["file"] = DmsFile
except ImportError:
pass
try:
from app.plugins.builtins.dms.models import Folder as DmsFolder
ENTITY_MODELS["folder"] = DmsFolder
except ImportError:
pass
try:
from app.plugins.builtins.calendar.models import CalendarEntry
ENTITY_MODELS["calendar_event"] = CalendarEntry
except ImportError:
pass
try:
from app.plugins.builtins.calendar.models import Calendar
ENTITY_MODELS["calendar"] = Calendar
except ImportError:
pass
try:
from app.plugins.builtins.calendar.models import Subtask
ENTITY_MODELS["subtask"] = Subtask
except ImportError:
pass
try:
@@ -78,8 +103,8 @@ try:
except ImportError:
pass
try:
from app.plugins.builtins.mail.models import Mailbox
ENTITY_MODELS["mailbox"] = Mailbox
from app.plugins.builtins.mail.models import MailAccount
ENTITY_MODELS["mailbox"] = MailAccount
except ImportError:
pass