T11: tags plugin + permissions plugin + entity links backend — 68 tests, 66.61% coverage
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""Entity Links plugin — link files to companies/contacts, reverse links, event cleanup."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.plugins.base import BasePlugin
|
||||
from app.plugins.manifest import PluginManifest, PluginRouteDef
|
||||
|
||||
|
||||
class EntityLinksPlugin(BasePlugin):
|
||||
"""Entity Links plugin for N:M file-to-entity links with event-driven cleanup."""
|
||||
|
||||
manifest = PluginManifest(
|
||||
name="entity_links",
|
||||
version="1.0.0",
|
||||
display_name="Entity Links",
|
||||
description="Link files to companies/contacts (N:M), reverse links, event cleanup on deletion.",
|
||||
dependencies=[],
|
||||
routes=[
|
||||
PluginRouteDef(
|
||||
path="/api/v1/dms",
|
||||
module="app.plugins.builtins.entity_links.routes",
|
||||
router_attr="router",
|
||||
),
|
||||
PluginRouteDef(
|
||||
path="/api/v1/companies",
|
||||
module="app.plugins.builtins.entity_links.routes",
|
||||
router_attr="company_router",
|
||||
),
|
||||
PluginRouteDef(
|
||||
path="/api/v1/contacts",
|
||||
module="app.plugins.builtins.entity_links.routes",
|
||||
router_attr="contact_router",
|
||||
),
|
||||
],
|
||||
events=["company.deleted", "contact.deleted"],
|
||||
migrations=["0001_initial.sql"],
|
||||
permissions=[],
|
||||
)
|
||||
|
||||
async def on_company_deleted(self, payload: dict[str, Any]) -> None:
|
||||
"""Handle company.deleted event — remove all EntityLink rows for that company."""
|
||||
from sqlalchemy import delete
|
||||
from app.core.db import get_session_factory
|
||||
from app.plugins.builtins.entity_links.models import EntityLink
|
||||
|
||||
entity_id = payload.get("entity_id") or payload.get("company_id")
|
||||
tenant_id = payload.get("tenant_id")
|
||||
if entity_id is None or tenant_id is None:
|
||||
return
|
||||
|
||||
import uuid as _uuid
|
||||
factory = get_session_factory()
|
||||
async with factory() as session:
|
||||
await session.execute(
|
||||
delete(EntityLink).where(
|
||||
EntityLink.tenant_id == _uuid.UUID(tenant_id),
|
||||
EntityLink.entity_type == "company",
|
||||
EntityLink.entity_id == _uuid.UUID(str(entity_id)),
|
||||
)
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
async def on_contact_deleted(self, payload: dict[str, Any]) -> None:
|
||||
"""Handle contact.deleted event — remove all EntityLink rows for that contact."""
|
||||
from sqlalchemy import delete
|
||||
from app.core.db import get_session_factory
|
||||
from app.plugins.builtins.entity_links.models import EntityLink
|
||||
|
||||
entity_id = payload.get("entity_id") or payload.get("contact_id")
|
||||
tenant_id = payload.get("tenant_id")
|
||||
if entity_id is None or tenant_id is None:
|
||||
return
|
||||
|
||||
import uuid as _uuid
|
||||
factory = get_session_factory()
|
||||
async with factory() as session:
|
||||
await session.execute(
|
||||
delete(EntityLink).where(
|
||||
EntityLink.tenant_id == _uuid.UUID(tenant_id),
|
||||
EntityLink.entity_type == "contact",
|
||||
EntityLink.entity_id == _uuid.UUID(str(entity_id)),
|
||||
)
|
||||
)
|
||||
await session.commit()
|
||||
Reference in New Issue
Block a user