Files

7.4 KiB

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/<plugin>/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_<event_name>

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
  • 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

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 %