T11: tags plugin + permissions plugin + entity links backend — 68 tests, 66.61% coverage

This commit is contained in:
leocrm-bot
2026-06-29 14:01:24 +02:00
parent 700b7a71ad
commit 5d1850768a
26 changed files with 2863 additions and 7 deletions
+154
View File
@@ -0,0 +1,154 @@
# 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
## 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 %