# T11 Briefing — Tags Plugin + Permissions Plugin + Entity Links Backend ## Project Root /a0/usr/workdir/dev-projects/leocrm ## Task Implement 3 builtin plugins: Tags, Permissions, Entity Links. ## Plugin Framework (existing — read these files first) - `app/plugins/base.py` — BasePlugin abstract class with lifecycle hooks - `app/plugins/manifest.py` — PluginManifest, PluginRouteDef schemas - `app/plugins/registry.py` — PluginRegistry (discovers builtins, manages lifecycle) - `app/plugins/builtins/test_sample.py` — Example plugin (reference pattern) - `app/plugins/builtins/migrations/` — Migration SQL files go here - `app/core/event_bus.py` — EventBus for pub/sub - `app/core/service_container.py` — DI container - `app/core/db.py` — Base, TenantMixin, TimestampMixin - `app/models/company.py` — Company model (reference for model patterns) - `app/models/plugin.py` — Plugin + PluginMigration models ## Architecture Rules - Plugins live in `app/plugins/builtins/` as subdirectories (e.g. `app/plugins/builtins/tags/`) - Each plugin has: `__init__.py` (exports plugin class), `plugin.py` (BasePlugin subclass), `routes.py` (APIRouter), `models.py` (SQLAlchemy models), `schemas.py` (Pydantic schemas), `migrations/` (SQL files) - Migrations are plain SQL files in `app/plugins/builtins//migrations/` - Models use SQLAlchemy 2.0 style (Mapped, mapped_column) with PGUUID, TenantMixin - Routes use FastAPI APIRouter, registered via manifest routes list - Events: subscribe in on_activate, handlers named `on_` ## 1. Tags Plugin (`app/plugins/builtins/tags/`) ### Requirements (F-TAG-01 through F-TAG-04) - Tags can be applied to files, folders, companies, contacts - Tags are global (not per-user), centrally managed - Multiple tags per entity (N:M) - Tag CRUD with color support - Tag filtering in lists (AND/OR combination) - Tag cloud/sidebar with entity counts ### Endpoints ``` GET /api/v1/tags → 200, list tags with entity counts POST /api/v1/tags → 201, create tag (name, color) PATCH /api/v1/tags/{id} → 200, update tag DELETE /api/v1/tags/{id} → 204, cascade delete assignments POST /api/v1/tags/assign → 200, assign tag to entity (tag_id, entity_type, entity_id) DELETE /api/v1/tags/assign → 204, remove tag assignment POST /api/v1/tags/bulk-assign → 200, assign multiple tags to entity GET /api/v1/tags/{id}/entities → 200, list entities with this tag ``` ### Models - `Tag`: id (UUID), name (str, unique per tenant), color (str, hex), tenant_id - `TagAssignment`: id, tag_id (FK), entity_type (str: company/contact/file/folder), entity_id (UUID), tenant_id ### Migration - `0001_initial.sql`: Create `tags` and `tag_assignments` tables with tenant_id columns ## 2. Permissions Plugin (`app/plugins/builtins/permissions/`) ### Requirements (F-PERM-01 through F-PERM-06) - Personal root folder per user ("Mein Bereich") - Shared root folders for teams/departments - Share files/folders with individual users (read/write) - Share files/folders with user groups (read/write) - Public share links (with password, expiry, download-only or preview+download) - Permission display (who has access?) ### Endpoints ``` GET /api/v1/dms/files/{id}/permissions → 200, permission list POST /api/v1/dms/files/{id}/permissions → 201, grant permission DELETE /api/v1/dms/files/{id}/permissions/{user_id} → 204, revoke POST /api/v1/dms/files/{id}/share-link → 200, create share link (returns public token URL) GET /api/public/share/{token} → 200 (file) or 410 (expired) DELETE /api/v1/dms/share-links/{id} → 204, revoke share link ``` ### Models - `Permission`: id, file_id (UUID), user_id (UUID), group_id (UUID nullable), access_level (read/write), tenant_id - `ShareLink`: id, file_id (UUID), token (str, unique), password_hash (nullable), expires_at (nullable), access_level (download/preview), tenant_id ### Migration - `0001_initial.sql`: Create `permissions` and `share_links` tables ### Special - Public share endpoint `/api/public/share/{token}` must NOT require auth - Expired links return 410 Gone - Password-protected links verify password before serving ## 3. Entity Links Backend (`app/plugins/builtins/entity_links/`) ### Requirements (F-LINK-01 through F-LINK-06) - Link files/folders to companies (N:M) - Link files/folders to contacts (N:M) - Reverse links (file shows linked entities) - Multi-links (one file → many entities) - Event cleanup: on company.deleted/contact.deleted → remove links ### Endpoints ``` POST /api/v1/dms/files/{id}/link → 200, link file to entity (entity_type, entity_id) DELETE /api/v1/dms/files/{id}/link → 204, remove link (entity_type, entity_id in body) GET /api/v1/dms/files/{id}/links → 200, list all linked entities for file GET /api/v1/companies/{id}/files → 200, list linked files for company GET /api/v1/contacts/{id}/files → 200, list linked files for contact ``` ### Models - `EntityLink`: id, file_id (UUID), entity_type (str: company/contact), entity_id (UUID), tenant_id, created_by (UUID) ### Migration - `0001_initial.sql`: Create `entity_links` table ### Event Handling - Subscribe to `company.deleted` → delete all EntityLink rows where entity_type='company' AND entity_id=deleted_id - Subscribe to `contact.deleted` → delete all EntityLink rows where entity_type='contact' AND entity_id=deleted_id ## Acceptance Criteria (14 total — ALL must pass) 1. GET /api/v1/dms/files/{id}/permissions → 200 + permission list 2. POST /api/v1/dms/files/{id}/link → 200, file linked to entity 3. DELETE /api/v1/dms/files/{id}/link → 204, link removed 4. POST /api/v1/dms/files/{id}/share-link → 200 + public token URL 5. GET /api/public/share/{token} with expired link → 410 6. GET /api/v1/tags → 200 + tags with counts 7. POST /api/v1/tags → 201, tag created 8. PATCH /api/v1/tags/{id} → 200 9. DELETE /api/v1/tags/{id} → 204, cascade delete assignments 10. POST /api/v1/tags/assign → 200, tag assigned to entity 11. DELETE /api/v1/tags/assign → 204, tag removed 12. POST /api/v1/tags/bulk-assign → 200, multiple tags assigned 13. DMS plugin listens to company.deleted event → linked files cleanup 14. Folder permissions enforced: user without read → 403 ## Test Files (create in `tests/`) - `tests/test_tags.py` — Tag CRUD, assignment, bulk assign, cascade delete, counts - `tests/test_permissions.py` — Personal root, shared root, share with users/groups, share links (password, expiry), permission display, 403 enforcement - `tests/test_entity_links.py` — Link file to company, link to contact, reverse links, multi-links, event cleanup on deletion ## Verification Commands ```bash cd /a0/usr/workdir/dev-projects/leocrm python -m pytest tests/test_tags.py tests/test_permissions.py tests/test_entity_links.py -v --tb=short python -m pytest tests/test_tags.py tests/test_permissions.py tests/test_entity_links.py --cov=app/plugins/builtins --cov-report=term-missing ``` ## Rules - Use text_editor:write for new files, text_editor:patch for updates - Read existing files before modifying - No Lorem Ipsum, no placeholder code - Follow existing patterns (SQLAlchemy 2.0, Pydantic v2, FastAPI APIRouter) - Each plugin must have manifest, plugin class, routes, models, schemas, migrations - Register plugins in `app/plugins/builtins/__init__.py` - Keep response under 50 lines - Report: files created, test count + pass/fail, coverage %